mirror of https://github.com/japl-lang/japl.git
Completed Python PoC
This commit is contained in:
parent
3c4ecb23dc
commit
cd1b8e937f
|
@ -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
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
from enum import Enum, auto
|
||||
|
||||
|
||||
class ClassType(Enum):
|
||||
|
||||
NONE = auto()
|
||||
CLASS = auto()
|
|
@ -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__()
|
||||
|
||||
|
||||
|
|
|
@ -5,3 +5,4 @@ class FunctionType(Enum):
|
|||
NONE = auto()
|
||||
FUNCTION = auto()
|
||||
METHOD = auto()
|
||||
INIT = auto()
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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"""
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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"""
|
||||
|
|
|
@ -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:
|
||||
|
|
15
README.md
15
README.md
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
Loading…
Reference in New Issue