Skip to content

Commit

Permalink
Added inheritance
Browse files Browse the repository at this point in the history
  • Loading branch information
Daniel Berezin committed Jun 15, 2019
1 parent b43415d commit 2d44ce6
Show file tree
Hide file tree
Showing 6 changed files with 97 additions and 32 deletions.
10 changes: 5 additions & 5 deletions docs/browser.e9f8be96.js

Large diffs are not rendered by default.

43 changes: 24 additions & 19 deletions examples/classExample.lox
Original file line number Diff line number Diff line change
@@ -1,28 +1,33 @@
// Class Example
class Lox {
breakfast() {
print "Toast";
this.hello = "toast";
return this.hello;
class Doughnut {
init(name) {
this.name = name;
}
}

fun hello () {
print "Would you like some breakfast?";
cook() {
var name = "";
if (this.name) {
name = this.name + " ";
}
print "You fry the " + name + "Doughnut until golden brown.";
}
}

print Lox;
class BostonCream < Doughnut {
init() {
super.init("Boston Cream");
}
}

print hello;
var loxInstance = Lox();
Doughnut("Cruller").cook();

loxInstance.category = "Breakfast";
print loxInstance.category;
print loxInstance.breakfast();
BostonCream().cook();

print Lox();
fun hello () {
print "Would you like some breakfast?";
}

// class Bagel {}
// var bagel = Bagel();
// print bagel; // Prints "Bagel instance".
// print bagel.lox;
print BostonCream;
print Doughnut;
print BostonCream();
print hello;
47 changes: 44 additions & 3 deletions interpreter.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const {
Literal,
Logical,
Class,
Super,
Get,
Set,
Var,
Expand Down Expand Up @@ -73,10 +74,11 @@ class LoxCallable {
}

class LoxClass extends LoxCallable {
constructor(name, methods) {
constructor(name, methods, superclass) {
super()
this.name = name
this.methods = methods
this.superclass = superclass
}

call(interpreter, args) {
Expand All @@ -86,6 +88,14 @@ class LoxClass extends LoxCallable {
return instance
}

getMethod(name, instance) {
if (this.methods.has(name)) return this.methods.get(name).bind(instance)

if (this.superclass) return this.superclass.getMethod(name, instance)

return null
}

toString() {
return `<${this.name}>`
}
Expand All @@ -101,7 +111,8 @@ class LoxInstance {
const name = token.lexeme
if (this.fields.has(name)) return this.fields.get(name)

if (this.klass.methods.has(name)) return this.klass.methods.get(name).bind(this)
const method = this.klass.getMethod(name, this)
if (method) return method

throw runtimeError(`Undefined property "${name}"`, token)
// return null
Expand Down Expand Up @@ -141,6 +152,7 @@ class Interpreter {
else if (expr instanceof Get) return this.visitGet(expr)
else if (expr instanceof Set) return this.visitSet(expr)
else if (expr instanceof This) return this.visitThis(expr)
else if (expr instanceof Super) return this.visitSuper(expr)
else if (expr instanceof Logical) return this.visitLogical(expr)
else if (expr instanceof Call) return this.visitCall(expr)
else if (expr instanceof While) return this.visitWhile(expr)
Expand Down Expand Up @@ -213,14 +225,32 @@ class Interpreter {
}

visitClass(stmt) {
let superClass = null
if (stmt.superclass) {
superClass = this.evaluate(stmt.superclass)
if (!(superClass instanceof LoxClass))
throw runtimeError('Superclass must be a class', stmt.superclass.name)
}

// We set the name before initializing it so classes can self-reference
this.environment.set(stmt.name, null)

if (superClass) {
this.environment = new Environment(this.environment)
this.environment.set({ lexeme: 'super' }, superClass)
}

const methods = new Map()
for (let method of stmt.methods) {
const fun = new LoxCallable(method, this.environment)
methods.set(method.name.lexeme, fun)
}
const klass = new LoxClass(stmt.name.lexeme, methods)

const klass = new LoxClass(stmt.name.lexeme, methods, superClass)

// Pop "Super" off the environment after creating the class
if (superClass) this.environment = this.environment.enclosing

this.environment.assign(stmt.name, klass)
return null
}
Expand Down Expand Up @@ -249,6 +279,17 @@ class Interpreter {
return this.environment.get({ name: expr.keyword })
}

visitSuper(expr) {
const methodName = expr.method.lexeme
const superclass = this.environment.get(new Var(expr.keyword))
const instance = this.environment.get(new Var({ lexeme: 'this' }))
const method = superclass.getMethod(methodName, instance)
if (!method) {
throw runtimeError(`Undefined property "${methodName}"`, expr.method)
}
return method
}

visitBlock(expr) {
this.interpretBlock(expr.statements, new Environment(this.environment))
return null
Expand Down
17 changes: 14 additions & 3 deletions parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const {
Literal,
While,
Class,
Super,
Get,
Set,
This,
Expand Down Expand Up @@ -53,6 +54,12 @@ class Parser {

classDeclaration() {
const name = this.consume(token.IDENTIFIER, `Expected class name`)

let superClass = null
if (this.match(token.LESS)) {
superClass = new Var(this.consume(token.IDENTIFIER, `Expected superclass name after "<"`))
}

this.consume(token.LEFT_BRACE, 'expected "{" before class body')

let methods = []
Expand All @@ -61,7 +68,7 @@ class Parser {
}

this.consume(token.RIGHT_BRACE, 'expected "}" after class body')
return new Class(name, methods)
return new Class(name, methods, superClass)
}

fun(type) {
Expand Down Expand Up @@ -293,6 +300,12 @@ class Parser {
if (this.match(token.TRUE)) return new Literal(true)
if (this.match(token.NIL)) return new Literal(null)
if (this.match(token.NUMBER, token.STRING)) return new Literal(this.previous().literal)
if (this.match(token.SUPER)) {
const keyword = this.previous()
this.consume(token.DOT, 'Expected "." after super statement')
const method = this.consume(token.IDENTIFIER, 'Expected superclass method name')
return new Super(keyword, method)
}
if (this.match(token.THIS)) return new This(this.previous())
if (this.match(token.IDENTIFIER)) return new Var(this.previous())

Expand All @@ -302,8 +315,6 @@ class Parser {
return new Grouping(expr)
}

console.log('primary')

throw ParseError('Expected Expression', this.peek())
}

Expand Down
1 change: 0 additions & 1 deletion tokenizer.js
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,6 @@ class Tokenizer {
this.handleIdentifiers()
} else {
// Column isn't -1 because we haven't iterated column yet
console.log(LoxError)
throw new LoxError(
`Unexpected character ${c}`,
this.startPosition,
Expand Down
11 changes: 10 additions & 1 deletion types.js
Original file line number Diff line number Diff line change
Expand Up @@ -116,9 +116,17 @@ class This {
}

class Class {
constructor(name, methods) {
constructor(name, methods, superclass = null) {
this.name = name
this.methods = methods
this.superclass = superclass
}
}

class Super {
constructor(keyword, method) {
this.keyword = keyword
this.method = method
}
}

Expand All @@ -140,6 +148,7 @@ module.exports = {
Class,
Get,
Set,
Super,
Literal,
Return,
Logical,
Expand Down

0 comments on commit 2d44ce6

Please sign in to comment.