Skip to content

Commit

Permalink
Added basic classes and fields
Browse files Browse the repository at this point in the history
  • Loading branch information
Daniel Berezin committed Jun 5, 2019
1 parent f74cd7f commit f130e5d
Show file tree
Hide file tree
Showing 7 changed files with 172 additions and 6 deletions.
5 changes: 5 additions & 0 deletions .vscodeignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.vscode/**
.vscode-test/**
docs/**
.gitignore
vsc-extension-quickstart.md
2 changes: 1 addition & 1 deletion browser.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ exampleProgram.onchange = e => {
}
let defaultProgram = 0
if (window.location.hash) {
defaultProgram = +(window.location.hash.replace(/[^\w\s]/gi, '').trim())
defaultProgram = +window.location.hash.replace(/[^\w\s]/gi, '').trim()
}
code.value = examplePrograms[defaultProgram].program

Expand Down
11 changes: 8 additions & 3 deletions cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ let options = {
prompt: '>'
}

const printReturnValue = lastLine =>
JSON.stringify(
lastLine && typeof lastLine.toString === 'function' ? lastLine.toString() : lastLine
)

const runPrompt = () => {
const prompt = options.prompt + ' '
process.stdout.write(prompt)
Expand All @@ -27,16 +32,16 @@ const runPrompt = () => {
let code = line
if (!line.endsWith(';') && !line.endsWith('}')) code += ';'
// TODO: Support multi-line block statements
const lastLine = run(code, env, options.debug)
console.log(JSON.stringify(lastLine))
const lastLine = run(code, env, console.log, options.debug)
console.log(printReturnValue(lastLine))
process.stdout.write(prompt)
})
}

const runFile = filename => {
try {
const file = fs.readFileSync(filename, 'utf8')
run(file, undefined, options.debug)
run(file, undefined, undefined, options.debug)
} catch (e) {
console.error(`YALI could not read the file ${filename}`)
console.error(e)
Expand Down
24 changes: 24 additions & 0 deletions examples/classExample.lox
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
class Lox {
breakfast() {
print "Toast";
}
}

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

print Lox;

print hello;
var loxInstance = Lox();

loxInstance.category = "Breakfast";
print loxInstance.category;

print Lox();

// class Bagel {}
// var bagel = Bagel();
// print bagel; // Prints "Bagel instance".
// print bagel.lox;
80 changes: 80 additions & 0 deletions interpreter.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ const {
Call,
Literal,
Logical,
Class,
Get,
Set,
Var,
Grouping,
Return,
Expand Down Expand Up @@ -53,6 +56,51 @@ class LoxCallable {
}
return null
}

toString() {
return `<${this.declaration.name.lexeme}()>`
}
}

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

call() {
return new LoxInstance(this)
}

toString() {
return `<${this.name}>`
}
}

class LoxInstance {
constructor(klass) {
this.klass = klass
this.fields = new Map()
}

get (token) {
const name = token.lexeme
if (this.fields.has(name)) {
return this.fields.get(name)
}

throw runtimeError(`Undefined property ${name}`, token)
// return null
}

set (token, value) {
const name = token.lexeme
this.fields.set(name, value)
}

toString () {
return `<+${this.klass.name}>`
}
}

class Interpreter {
Expand All @@ -75,6 +123,9 @@ class Interpreter {
if (expr instanceof Block) return this.visitBlock(expr)
else if (expr instanceof LoxFunction) return this.visitFunction(expr)
else if (expr instanceof Assignment) return this.visitAssignment(expr)
else if (expr instanceof Class) return this.visitClass(expr)
else if (expr instanceof Get) return this.visitGet(expr)
else if (expr instanceof Set) return this.visitSet(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 @@ -146,6 +197,35 @@ class Interpreter {
return null
}

visitClass(stmt) {
// We set the name before initializing it so classes can self-reference
this.environment.set(stmt.name, null)
const klass = new LoxClass(stmt.name.lexeme)
// console.log(klass)
this.environment.assign(stmt.name, klass)
return null
}

visitGet(expr) {
const object = this.evaluate(expr.object)
if (object instanceof LoxInstance) {
return object.get(expr.name)
}

throw runtimeError('Only instances have properties', expr.name)
}

visitSet(expr) {
const object = this.evaluate(expr.object)
if (!(object instanceof LoxInstance)) {
throw runtimeError('Only instances have fields', expr.name)
}

var val = this.evaluate(expr.value)

return object.set(expr.name, val)
}

visitBlock(expr) {
this.interpretBlock(expr.statements, new Environment(this.environment))
return null
Expand Down
27 changes: 26 additions & 1 deletion parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ const {
Call,
Literal,
While,
Class,
Get,
Set,
Grouping,
Return,
LoxFunction,
Expand All @@ -21,6 +24,7 @@ const { parseError: ParseError } = require('./errors')
const token = tokenizer.tokenEnum

const FUNCTION_TYPE = 'function'
const METHOD_TYPE = 'method'

class Parser {
constructor(tokens) {
Expand All @@ -43,15 +47,29 @@ class Parser {

declaration() {
if (this.match(token.FUN)) return this.fun(FUNCTION_TYPE)
if (this.match(token.CLASS)) return this.classDeclaration()
if (this.match(token.VAR)) return this.varDeclaration()

return this.statement()
}

classDeclaration() {
const name = this.consume(token.IDENTIFIER, `Expected class name`)
this.consume(token.LEFT_BRACE, 'expected "{" before class body')

let methods = []
while (!this.check(token.RIGHT_BRACE)) {
methods.push(this.fun(METHOD_TYPE))
}

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

fun(type) {
const name = this.consume(token.IDENTIFIER, `Expected ${type} name`)

const params = []
let params = []
this.consume(token.LEFT_PAREN, `Expected paren after ${type} name`)
if (!this.check(token.RIGHT_PAREN)) {
do {
Expand Down Expand Up @@ -104,6 +122,8 @@ class Parser {
if (expr instanceof Var) {
const nameToken = expr.name
return new Assignment(nameToken, value)
} else if (expr instanceof Get) {
return new Set(expr.object, expr.name, value)
}
throw ParseError('Expected Expression', equalToken)
}
Expand Down Expand Up @@ -243,6 +263,9 @@ class Parser {
while (true) {
if (this.match(token.LEFT_PAREN)) {
expr = this.finishCall(expr)
} else if (this.match(token.DOT)) {
const name = this.consume(token.IDENTIFIER, 'Expected property name after "."')
expr = new Get(expr, name)
} else {
break
}
Expand Down Expand Up @@ -275,6 +298,8 @@ class Parser {
return new Grouping(expr)
}

console.log('primary')

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

Expand Down
29 changes: 28 additions & 1 deletion types.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,21 @@ class Var {
}
}

class Get {
constructor(object, name) {
this.object = object
this.name = name
}
}

class Set {
constructor(object, name, value) {
this.object = object
this.name = name
this.value = value
}
}

class Grouping {
constructor(expression) {
this.expression = expression
Expand Down Expand Up @@ -73,11 +88,13 @@ class While {

class Call {
constructor(callee, paren, args) {
;(this.callee = callee), (this.paren = paren)
this.callee = callee
this.paren = paren
this.arguments = args
}
}

// Runtime Classes
class Callable {
constructor(name, func) {
this.lexeme = name
Expand All @@ -96,6 +113,13 @@ class Return {
}
}

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

class LoxFunction {
constructor(name, params, bodyStatements) {
this.name = name
Expand All @@ -112,6 +136,9 @@ module.exports = {
Call,
Callable,
While,
Class,
Get,
Set,
Literal,
Return,
Logical,
Expand Down

0 comments on commit f130e5d

Please sign in to comment.