Completed Python PoC

This commit is contained in:
nocturn9x 2020-08-01 11:21:21 +02:00
parent 3c4ecb23dc
commit cd1b8e937f
13 changed files with 202 additions and 27 deletions

View File

@ -160,12 +160,24 @@ class Interpreter(Expression.Visitor, Statement.Visitor):
def visit_class(self, stmt: Class):
"""Visits a class declaration"""
superclass = None
if stmt.superclass:
superclass = self.eval(stmt.superclass)
if not isinstance(superclass, JAPLClass):
raise JAPLError(stmt.superclass.name, "Superclass must be a class")
self.environment.define(stmt.name.lexeme, None)
if superclass:
environment = Environment(self.environment)
environment.define("super", superclass)
else:
environment = self.environment
methods = {}
for method in stmt.methods:
func = JAPLFunction(method, self.environment)
func = JAPLFunction(method, environment)
methods[method.name.lexeme] = func
klass = JAPLClass(stmt.name.lexeme, methods)
klass = JAPLClass(stmt.name.lexeme, methods, superclass)
if superclass:
self.environment = environment.enclosing
self.environment.assign(stmt.name, klass)
def visit_while(self, statement: While):
@ -305,6 +317,22 @@ class Interpreter(Expression.Visitor, Statement.Visitor):
value = self.eval(expr.value)
obj.set(expr.name, value)
def visit_this(self, expr):
"""Evaluates 'this' expressions"""
return self.lookup(expr.keyword, expr)
def visit_super(self, expr):
"""Evaluates 'super' expressions"""
distance = self.locals.get(expr)
superclass = self.environment.get_at(distance, "super")
instance = self.environment.get_at(distance - 1, "this")
meth = superclass.get_method(expr.method.lexeme)
if not meth:
raise JAPLError(expr.method, f"Undefined property '{expr.method.lexeme}'")
return meth.bind(instance)
def exec(self, statement: Statement):
"""
Executes a statement

7
JAPL/meta/classtype.py Normal file
View File

@ -0,0 +1,7 @@
from enum import Enum, auto
class ClassType(Enum):
NONE = auto()
CLASS = auto()

View File

@ -140,3 +140,28 @@ class Set(Expression):
def accept(self, visitor):
return visitor.visit_set(self)
@dataclass
class This(Expression):
keyword: Token
def accept(self, visitor):
return visitor.visit_this(self)
def __hash__(self):
return super().__hash__()
@dataclass
class Super(Expression):
keyword: Token
method: Token
def accept(self, visitor):
return visitor.visit_super(self)
def __hash__(self):
return super().__hash__()

View File

@ -5,3 +5,4 @@ class FunctionType(Enum):
NONE = auto()
FUNCTION = auto()
METHOD = auto()
INIT = auto()

View File

@ -1,6 +1,6 @@
from abc import ABC, abstractmethod
from dataclasses import dataclass
from .expression import Expression
from .expression import Expression, Variable
from .tokenobject import Token
from typing import List, Any
@ -181,6 +181,7 @@ class Class(Statement):
name: Token
methods: list
superclass: Variable
def accept(self, visitor):
visitor.visit_class(self)

View File

@ -2,7 +2,7 @@ from .meta.exceptions import ParseError
from .meta.tokentype import TokenType
from .meta.tokenobject import Token
from typing import List, Union
from .meta.expression import Variable, Assignment, Logical, Call, Binary, Unary, Literal, Grouping, Expression, Get, Set
from .meta.expression import Variable, Assignment, Logical, Call, Binary, Unary, Literal, Grouping, Expression, Get, Set, This, Super
from .meta.statement import Print, StatementExpr, Var, Del, Block, If, While, Break, Function, Return, Class
@ -29,7 +29,7 @@ class Parser(object):
def throw(self, token: Token, message: str) -> ParseError:
"""Returns ParseError with the given message"""
return ParseError(message, token)
return ParseError(token, message)
def synchronize(self):
"""Synchronizes the parser's state to recover after
@ -109,6 +109,13 @@ class Parser(object):
return Grouping(expr)
elif self.match(TokenType.ID):
return Variable(self.previous())
elif self.match(TokenType.SUPER):
keyword = self.previous()
self.consume(TokenType.DOT, "Expecting '.' after 'super'")
method = self.consume(TokenType.ID, "Expecting superclass method name")
return Super(keyword, method)
elif self.match(TokenType.THIS):
return This(self.previous())
raise self.throw(self.peek(), "Invalid syntax")
def finish_call(self, callee):
@ -406,12 +413,16 @@ class Parser(object):
"""Parses a class declaration"""
name = self.consume(TokenType.ID, "Expecting class name")
superclass = None
if self.match(TokenType.LT):
self.consume(TokenType.ID, "Expecting superclass name")
superclass = Variable(self.previous())
self.consume(TokenType.LB, "Expecting '{' before class body")
methods = []
while not self.check(TokenType.RB) and not self.done():
methods.append(self.function("method"))
self.consume(TokenType.RB, "Expecting '}' after class body")
return Class(name, methods)
return Class(name, methods, superclass)
def declaration(self):
"""Parses a declaration"""

View File

@ -2,6 +2,7 @@ from .meta.exceptions import JAPLError
from .meta.expression import Expression
from .meta.statement import Statement
from .meta.functiontype import FunctionType
from .meta.classtype import ClassType
from .meta.looptype import LoopType
try:
from functools import singledispatchmethod
@ -26,6 +27,7 @@ class Resolver(Expression.Visitor, Statement.Visitor):
self.scopes = deque()
self.current_function = FunctionType.NONE
self.current_loop = LoopType.NONE
self.current_class = ClassType.NONE
@singledispatchmethod
def resolve(self, stmt_or_expr: Union[Statement, Expression, List[Statement]]):
@ -151,11 +153,29 @@ class Resolver(Expression.Visitor, Statement.Visitor):
self.resolve_function(stmt, FunctionType.FUNCTION)
def visit_class(self, stmt):
"""Visits a class statement"""
enclosing = self.current_class
self.current_class = ClassType.CLASS
self.declare(stmt.name)
self.define(stmt.name)
if stmt.superclass:
if stmt.superclass.name.lexeme == stmt.name.lexeme:
raise JAPLError(stmt.name, "A class cannot inherit from itself")
self.resolve(stmt.superclass)
self.begin_scope()
self.scopes[-1]["super"] = True
self.begin_scope()
self.scopes[-1]["this"] = True
for method in stmt.methods:
ftype = FunctionType.METHOD
if method.name.lexeme == "init":
ftype = FunctionType.INIT
self.resolve_function(method, ftype)
self.end_scope()
if stmt.superclass:
self.end_scope()
self.current_class = enclosing
def visit_statement_expr(self, stmt):
"""Visits a statement expression node"""
@ -180,7 +200,9 @@ class Resolver(Expression.Visitor, Statement.Visitor):
if self.current_function == FunctionType.NONE:
raise JAPLError(stmt.keyword, "'return' outside function")
if stmt.value is not None:
elif self.current_function == FunctionType.INIT:
raise JAPLError(stmt.keyword, "Cannot explicitly return from constructor")
elif stmt.value is not None:
self.resolve(stmt.value)
def visit_while(self, stmt):
@ -246,3 +268,19 @@ class Resolver(Expression.Visitor, Statement.Visitor):
self.resolve(expr.value)
self.resolve(expr.object)
def visit_this(self, expr):
"""Visits a 'this' expression"""
if self.current_class == ClassType.NONE:
raise JAPLError(expr.keyword, "'this' outside class")
self.resolve_local(expr, expr.keyword)
def visit_super(self, expr):
"""Visits a 'super' expression"""
if self.current_class == ClassType.NONE:
raise JAPLError(expr.keyword, "'super' outside class")
self.resolve_local(expr, expr.keyword)

View File

@ -15,8 +15,9 @@ class JAPLInstance:
def get(self, name: Token):
if name.lexeme in self.fields:
return self.fields[name.lexeme]
elif name.lexeme in self.klass.methods:
return self.klass.methods[name.lexeme]
meth = self.klass.get_method(name.lexeme)
if meth:
return meth.bind(self)
raise JAPLError(name, f"Undefined property '{name.lexeme}'")
def set(self, name: Token, value: object):

View File

@ -5,13 +5,28 @@ from .instance import JAPLInstance
class JAPLClass(Callable):
"""A JAPL class"""
def __init__(self, name: str, methods: dict):
def __init__(self, name: str, methods: dict, superclass):
self.name = name
self.methods = methods
self.arity = 0
self.superclass = superclass
if self.get_method("init"):
self.arity = self.get_method("init").arity
else:
self.arity = 0
def get_method(self, name: str):
if name in self.methods:
return self.methods[name]
elif self.superclass and name in self.superclass.methods:
return self.superclass.methods[name]
def __repr__(self):
return f"<class '{self.name}'>"
def call(self, interpreter, arguments):
return JAPLInstance(self)
instance = JAPLInstance(self)
constructor = self.get_method("init")
if constructor:
constructor.bind(instance).call(interpreter, arguments)
return instance

View File

@ -41,24 +41,30 @@ class JAPLFunction(Callable):
"""Object constructor"""
self.declaration = declaration
self._repr = f"<function {self.declaration.name.lexeme}>"
self.arity = len(self.declaration.params)
self.closure = closure
def bind(self, obj: object):
"""Binds a method to an object"""
env = Environment(self.closure)
env.define("this", obj)
func = type(self)(self.declaration, env)
func._repr = f"<bound method {func.declaration.name.lexeme} of object {obj.klass.name}>"
return func
def call(self, interpreter, arguments):
scope = Environment(self.closure)
for name, value in zip(self.declaration.params, arguments):
scope.define(name.lexeme, value)
interpreter.in_function = True
try:
interpreter.execute_block(self.declaration.body, scope)
except ReturnException as error:
interpreter.in_function = False
return error.args[0]
interpreter.in_function = False
def __repr__(self):
return f"<function {self.declaration.name.lexeme}>"
return self._repr
class Truthy(Callable):
"""JAPL's wrapper around bool"""

View File

@ -40,7 +40,6 @@ class JAPL(object):
def repl(self):
"""Starts an interactive REPL"""
self.interpreter = Interpreter()
print("[JAPL 0.1.1 - Interactive REPL]")
while True:
try:
@ -60,7 +59,7 @@ class JAPL(object):
try:
ast = Parser(tokens).parse()
except ParseError as err:
message, token = err.args
token, message = err.args
print(f"An exception occurred at line {token.line} at '{token.lexeme}': {message}")
else:
try:

View File

@ -8,18 +8,21 @@ for Just Another Programming Language, but beware! Despite the name, the name is
## Some backstory
JAPL is born thanks to the amazing work of Bob (whose surname is obscure) that wrote a book available completely for free
at [this](https://craftinginterpreters.com) link.
JAPL is born thanks to the amazing work of Bob Nystrom that wrote a book available completely for free
at [this](https://craftinginterpreters.com) link, where he describes the implementation of a simple language called Lox.
Even though that books treats the implementation of a basic language named Lox, JAPL is (will, actually) much more feature-rich:
### What has been added from Lox
- Possibility to delete variables with the `del` statement
- `break` statement
- Nested comments (__Coming Soon__)
- `continue` statement (__Coming Soon__)
- multi-line comments
- Nested comments
- Generators (__Coming soon__)
- A decent standard library (__Work in progress__)
- A decent standard library with collections, I/O utilities and such (__Work in progress__)
Other than that, JAPL features closures, function definitions, classes and static scoping. You can check
Other than that, JAPL features closures, function definitions, classes, inheritance and static scoping. You can check
the provided example `.jpl` files in the repo to find out more about JAPL.
### Disclaimer

View File

@ -27,10 +27,13 @@ var b = "global1";
print a;
print b; // The outer scope isn't affected
/*
A multiline comment
yay!
*/
// Control flow statements
var n = 0;
while (n <= 10) { // While loops
if (n <= 5) { // If statements
@ -49,7 +52,7 @@ for (var i = 0; i < 10; i = i + 1) { // For loops
print clock(); // Function calls
fun count(n) { // Function definitions
if (n > 1) count(n - 1);
if (n > 1) count(n - 1); // Recursion works
print n;
}
@ -83,3 +86,40 @@ fun makeCounter() {
var counter = makeCounter();
counter(); // "1".
counter(); // "2".
// Classes
class Person {
init(name) { // Class initializer
this.name = name;
}
greet() { // Methods don't use the 'fun' keyword!
print "Hello, " + this.name;
}
}
var bob = Person("Bob"); // Object creation
bob.greet(); // Prints Hello, Bob
var greetbob = bob.greet; // Functions and methods are first-class objects! (classes are too)
greetbob();
class Male < Person { // Male inherits from person
init(name) {
super.init(name); // Inherits constructor behavior
this.sex = "male";
}
greet() {
super.greet(); // Inherits behavior from superclass
}
}
var mark = Male("Mark");
mark.greet();