Skip to content

Commit 6932039

Browse files
committed
add: inheritance, complete part 2
1 parent 874f3e6 commit 6932039

File tree

14 files changed

+291
-19
lines changed

14 files changed

+291
-19
lines changed

GRAMMAR.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@ declaration = const_declaration
44
| function_declaration
55
| class_declaration
66
| statement
7-
class_declaration = "class" IDENTIFIER "{" function* "}"
7+
class_declaration = "class" IDENTIFIER (":" IDENTIFIER)? "{" method* "}"
8+
method = function
9+
| "static" function
10+
| IDENTIFIER block
811
function_declaration = "fun" function
912
function = IDENTIFIER "(" parameters? ")" block
1013
parameters = IDENTIFIER ( "," IDENTIFIER ) *

README.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,4 +235,31 @@ class Circle {
235235
236236
const c = Circle(10);
237237
println c.area; // 314.15
238+
```
239+
240+
20) Implemented inheritance with a similar style to Python. Derived class must call `super.init` to initialize parent class properties.
241+
```
242+
class Animal {
243+
init(name) {
244+
this.name = name;
245+
}
246+
247+
speak {
248+
return this.name + " makes a sound.";
249+
}
250+
}
251+
252+
class Dog : Animal {
253+
init(name, breed) {
254+
super.init(name);
255+
this.breed = breed;
256+
}
257+
258+
speak {
259+
return this.name + " barks.";
260+
}
261+
}
262+
263+
const dog = Dog("Fido", "Golden Retriever");
264+
println dog.speak;
238265
```

samples/inheritance.lox

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
class Animal {
2+
init(name) {
3+
this.name = name;
4+
}
5+
6+
speak {
7+
return this.name + " makes a sound.";
8+
}
9+
}
10+
11+
class Dog : Animal {
12+
init(name, breed) {
13+
super.init(name);
14+
this.breed = breed;
15+
}
16+
17+
speak {
18+
return this.name + " barks.";
19+
}
20+
}
21+
22+
class FriendlyDog: Dog {
23+
init(name, breed) {
24+
super.init(name, breed);
25+
}
26+
27+
speak {
28+
return this.name + ", a friendly " + this.breed + " barks";
29+
}
30+
31+
static getMood() {
32+
return "Friendly";
33+
}
34+
}
35+
36+
const dog = FriendlyDog("Fido", "Golden Retriever");
37+
println dog.speak;
38+
println "Currently the dog is " + dog.getMood();

src/python_lox/ast/expr.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,10 @@ def visit_set_expr(self, expr: "Set") -> T:
6363
def visit_this_expr(self, expr: "This") -> T:
6464
pass
6565

66+
@abstractmethod
67+
def visit_super_expr(self, expr: "Super") -> T:
68+
pass
69+
6670

6771
class Expr(ABC):
6872
@abstractmethod
@@ -186,3 +190,11 @@ class This(Expr):
186190

187191
def accept(self, visitor: Visitor[T]) -> T:
188192
return visitor.visit_this_expr(self)
193+
194+
195+
@dataclass
196+
class Super(Expr):
197+
keyword: Token
198+
199+
def accept(self, visitor: Visitor[T]) -> T:
200+
return visitor.visit_super_expr(self)

src/python_lox/ast/stmt.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from typing import Generic, List, TypeVar
44

55
from ..token import Token
6-
from .expr import Expr
6+
from .expr import Expr, Variable
77

88
T = TypeVar("T")
99

@@ -206,6 +206,7 @@ class Class(Stmt):
206206
methods: List[Function]
207207
static_methods: List[Function]
208208
getters: List[Function]
209+
base_class: Variable | None = None
209210

210211
def accept(self, visitor: Visitor[T]) -> T:
211212
return visitor.visit_class_stmt(self)

src/python_lox/callable.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ def call(self, interpreter: "Interpreter", args: List[object]) -> object:
7272
def bind(self, instance: "LoxInstance") -> "LoxFunction":
7373
environment = Environment(parent=self.closure)
7474
environment.define("this", instance)
75+
if instance.base_class_instance is not None:
76+
environment.define("super", instance.base_class_instance)
7577
return LoxFunction(
7678
declaration=self.declaration,
7779
closure=environment,

src/python_lox/extras/ast_printer.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,3 +71,7 @@ def visit_set_expr(self, expr: Expr.Set) -> str:
7171
@override
7272
def visit_this_expr(self, expr: Expr.This) -> str:
7373
return "(this)"
74+
75+
@override
76+
def visit_super_expr(self, expr: Expr.Super) -> str:
77+
return "(super)"

src/python_lox/interpreter.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,16 @@ def visit_class_stmt(self, stmt: Stmt.Class) -> None:
377377
methods: Dict[str, LoxFunction] = {}
378378
static_methods: Dict[str, LoxFunction] = {}
379379
getters: Dict[str, LoxFunction] = {}
380+
base_class: LoxClass | None = None
381+
382+
if stmt.base_class is not None:
383+
cls = self.evaluate(stmt.base_class)
384+
if not isinstance(cls, LoxClass):
385+
raise RuntimeException(
386+
f'Error: class "{stmt.name.string_repr}" cannot derive from "{stmt.base_class.name.string_repr}" since "{stmt.base_class.name.string_repr}" is not a class',
387+
token=stmt.name,
388+
)
389+
base_class = cls
380390

381391
for method in stmt.methods:
382392
function = LoxFunction(
@@ -402,7 +412,9 @@ def visit_class_stmt(self, stmt: Stmt.Class) -> None:
402412
)
403413
getters[getter.name.string_repr] = function
404414

405-
classobj = LoxClass(stmt.name.string_repr, methods, getters=getters)
415+
classobj = LoxClass(
416+
stmt.name.string_repr, methods, getters=getters, base_class=base_class
417+
)
406418
classobj.class_.methods = static_methods
407419
self.environment.define(stmt.name, classobj)
408420

@@ -431,6 +443,10 @@ def visit_set_expr(self, expr: Expr.Set) -> object:
431443
def visit_this_expr(self, expr: Expr.This) -> object:
432444
return self.lookup_variable(expr.keyword, expr)
433445

446+
@override
447+
def visit_super_expr(self, expr: Expr.Super) -> object:
448+
return self.lookup_variable(expr.keyword, expr)
449+
434450
def execute(self, statement: Stmt.Stmt) -> None:
435451
statement.accept(self)
436452

src/python_lox/lox_class.py

Lines changed: 44 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,12 @@
99

1010

1111
class LoxInstance:
12-
def __init__(self, class_: "LoxClass") -> None:
12+
def __init__(
13+
self, class_: "LoxClass", base_class_instance: "LoxInstance | None" = None
14+
) -> None:
1315
self.class_ = class_
1416
self.fields: Dict[str, object] = {}
17+
self.base_class_instance: LoxInstance | None = base_class_instance
1518

1619
def __str__(self) -> str:
1720
return f"<{self.class_.name()} instance at 0x{id(self):x}>"
@@ -34,6 +37,18 @@ def get(self, name: Token, interpreter: "Interpreter") -> object:
3437
if name.string_repr in self.class_.class_.methods:
3538
return self.class_.class_.methods[name.string_repr]
3639

40+
if self.base_class_instance is not None:
41+
try:
42+
return self.base_class_instance.get(name, interpreter)
43+
except RuntimeException as r:
44+
if str(r).startswith("Error: There is no attribute"):
45+
raise RuntimeException(
46+
f'Error: There is no attribute "{name.string_repr}" on an instance of class "{self.class_.name()}"',
47+
token=name,
48+
)
49+
else:
50+
raise r
51+
3752
raise RuntimeException(
3853
f'Error: There is no attribute "{name.string_repr}" on an instance of class "{self.class_.name()}"',
3954
token=name,
@@ -49,12 +64,14 @@ def __init__(
4964
name_: str,
5065
methods: Dict[str, LoxFunction],
5166
getters: Dict[str, LoxFunction] = {},
67+
base_class: "LoxClass | None" = None,
5268
) -> None:
5369
LoxInstance.__init__(self, Meta())
5470
Callable.__init__(self)
5571
self.name_ = name_
5672
self.methods = methods
5773
self.getters = getters
74+
self.base_class = base_class
5875

5976
def __str__(self) -> str:
6077
return f"<class {self.name_}>"
@@ -71,11 +88,21 @@ def arity(self) -> int:
7188
return constructor.arity()
7289

7390
@override
74-
def call(self, interpreter: "Interpreter", args: List[object]) -> object:
75-
instance = LoxInstance(class_=self)
76-
constructor = self.methods.get("init")
77-
if constructor is not None:
78-
constructor.bind(instance).call(interpreter, args)
91+
def call(
92+
self,
93+
interpreter: "Interpreter",
94+
args: List[object],
95+
do_not_call_init: bool = False,
96+
) -> object:
97+
base_class_instance: LoxInstance | None = None
98+
if self.base_class:
99+
base_class_instance = self.base_class.call(interpreter, [], True) # type: ignore
100+
101+
instance = LoxInstance(class_=self, base_class_instance=base_class_instance)
102+
if not do_not_call_init:
103+
constructor = self.methods.get("init")
104+
if constructor is not None:
105+
constructor.bind(instance).call(interpreter, args)
79106
return instance
80107

81108

@@ -86,6 +113,7 @@ def __init__(self) -> None:
86113
self.name_ = "Meta"
87114
self.methods: Dict[str, LoxFunction] = {}
88115
self.getters: Dict[str, LoxFunction] = {}
116+
self.base_class = None
89117

90118
def __str__(self) -> str:
91119
return f"<meta {self.name_}>"
@@ -102,9 +130,15 @@ def arity(self) -> int:
102130
return constructor.arity()
103131

104132
@override
105-
def call(self, interpreter: "Interpreter", args: List[object]) -> object:
133+
def call(
134+
self,
135+
interpreter: "Interpreter",
136+
args: List[object],
137+
do_not_call_init: bool = False,
138+
) -> object:
106139
instance = LoxInstance(class_=self)
107-
constructor = self.methods.get("init")
108-
if constructor is not None:
109-
constructor.bind(instance).call(interpreter, args)
140+
if not do_not_call_init:
141+
constructor = self.methods.get("init")
142+
if constructor is not None:
143+
constructor.bind(instance).call(interpreter, args)
110144
return instance

src/python_lox/parser.py

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,9 @@ def primary(self) -> expr.Expr:
348348
if self.match([TokenType.THIS]):
349349
return expr.This(keyword=self.previous())
350350

351+
if self.match([TokenType.SUPER]):
352+
return expr.Super(keyword=self.previous())
353+
351354
if self.match([TokenType.NIL]):
352355
return expr.Literal(value=None)
353356

@@ -396,7 +399,10 @@ def primary(self) -> expr.Expr:
396399
self.factor()
397400
raise ParserException("Left hand operand missing", token=err_token)
398401

399-
raise ParserException("Expected expression", token=self.previous())
402+
tok = self.peek()
403+
if tok.token_type == TokenType.EOF:
404+
tok = self.previous()
405+
raise ParserException("Expected expression", token=tok)
400406

401407
def arrow_function(self) -> None | expr.Arrow:
402408
prev_current: int = self.current
@@ -474,7 +480,7 @@ def if_statement(self) -> stmt.If:
474480
except ParserException as e:
475481
if str(e) == "Expected expression":
476482
raise ParserException(
477-
"Expected valid expression after if", token=self.previous()
483+
"Expected valid expression after if", token=e.token
478484
)
479485
else:
480486
raise e
@@ -498,7 +504,7 @@ def while_statement(self) -> stmt.Stmt:
498504
except ParserException as e:
499505
if str(e) == "Expected expression":
500506
raise ParserException(
501-
"Expected valid expression after while", token=self.previous()
507+
"Expected valid expression after while", token=e.token
502508
)
503509
else:
504510
raise e
@@ -644,6 +650,14 @@ def const_declaration(self) -> stmt.Const:
644650
def class_declaration(self) -> stmt.Class:
645651
self.consume([TokenType.IDENTIFIER], message="Expected class name")
646652
name = self.previous()
653+
654+
base_class: expr.Variable | None = None
655+
if self.match([TokenType.COLON]):
656+
self.consume(
657+
[TokenType.IDENTIFIER], message='Expected base class name after ":"'
658+
)
659+
base_class = expr.Variable(name=self.previous())
660+
647661
self.consume([TokenType.LEFT_BRACE], message='Expected "{" after class name')
648662

649663
methods: List[stmt.Function] = []
@@ -673,7 +687,11 @@ def class_declaration(self) -> stmt.Class:
673687

674688
self.consume([TokenType.RIGHT_BRACE], message='Expected "}" after class body')
675689
return stmt.Class(
676-
name=name, methods=methods, static_methods=static_methods, getters=getters
690+
name=name,
691+
methods=methods,
692+
static_methods=static_methods,
693+
getters=getters,
694+
base_class=base_class,
677695
)
678696

679697
def declaration(self) -> stmt.Stmt:
@@ -692,7 +710,11 @@ def parse(self, repl: bool = False) -> List[stmt.Stmt] | None:
692710
while not self.is_at_end():
693711
prev_current = self.current
694712
try:
695-
statements.append(self.declaration())
713+
# Ignore empty statements
714+
while self.check(TokenType.SEMICOLON):
715+
self.advance()
716+
if not self.is_at_end():
717+
statements.append(self.declaration())
696718
except ParserException as e:
697719
if not repl:
698720
if self.error_reporter is None:

0 commit comments

Comments
 (0)