DzLox

:)
git clone https://git.sr.ht/~ashymad/DzLox
Log | Files | Refs | Submodules | LICENSE

commit 436ebdb358c0c907ac0253c9ad42a2b98244d73a
parent 4902b342c1d5a728ff5219f3d9bb0074c9f1135a
Author: Szymon Mikulicz <szymon.mikulicz@posteo.net>
Date:   Sat, 15 Oct 2022 20:02:48 +0200

Add class methods/fields

Diffstat:
Msource/ast.d | 2+-
Msource/cls.d | 34+++++++++++++++++++++-------------
Msource/instance.d | 44++++++++++++++++++++++++++++++++++++--------
Msource/interpreter.d | 2+-
Msource/parser.d | 14+++++++++-----
Msource/resolver.d | 11++++++++---
6 files changed, 76 insertions(+), 31 deletions(-)

diff --git a/source/ast.d b/source/ast.d @@ -13,7 +13,7 @@ static immutable string[][] expressions = [ ["Logical", "Expr left", "TokenI operator", "Expr right"], ["Call", "Expr callee", "TokenI paren", "Expr[] arguments"], ["Function", "TokenI[] params", "Stmt[] body"], - ["Class", "Var[] methods"], + ["Class", "Var[] methods", "Var[] classmethods"], ["Get", "Expr object", "TokenI name"], ["Set", "Expr object", "TokenI name", "Expr value"], ["This", "TokenI keyword"], diff --git a/source/cls.d b/source/cls.d @@ -6,11 +6,12 @@ import ast; import fun; import std.array; -class Cls : Callable { +class Cls : Instance, Callable { private Var[] props; private ulong _arity; + private Cls meta; - this(Var[] props) { + this(Var[] props, Var[] classprops = null, Interpreter interpreter = null) { this.props = props; _arity = 0; foreach(prop; props) { @@ -20,30 +21,37 @@ class Cls : Callable { } } } + if (classprops !is null) { + this.meta = new Cls(classprops); + super(meta.evalProps(interpreter)); + super.construct([], interpreter); + } else { + this.meta = null; + super(); + } } Variant call(Interpreter interpreter, Variant[] arguments) { + Variant[string] fields = evalProps(interpreter); + Instance instance = new Instance(fields); + instance.addFields(super.getFields()); + instance.construct(arguments, interpreter); + return Variant(instance); + } + + private Variant[string] evalProps(Interpreter interpreter) { Variant[string] fields = null; foreach(prop; props) { fields[prop.name.lexeme] = interpreter.evaluate(prop.initializer); } - Instance instance = new Instance(this, fields); - if (auto fun = "init" in fields) { - if (fun.convertsTo!(Fun)) { - auto ifun = fun.get!(Fun); - ifun.setInitializer(); - ifun.bind(instance).call(interpreter, arguments); - *fun = Variant(ifun); - } - } - return Variant(instance); + return fields; } ulong arity() { return _arity; } - void toString(scope void delegate(const(char)[]) sink) const { + override void toString(scope void delegate(const(char)[]) sink) const { sink("<class>"); } } diff --git a/source/instance.d b/source/instance.d @@ -1,28 +1,29 @@ -import cls; import token; import error; import std.variant; import fun; +import interpreter; +import std.range; +import std.algorithm; class Instance { - private Cls cls; private Variant[string] fields; + private Fun constructor; - this(Cls cls, Variant[string] fields) { - this.cls = cls; + this(Variant[string] fields = null) { this.fields = fields; + this.constructor = null; + if (this.fields) bindMethods(); } this(Instance inst) { - this.cls = inst.cls; this.fields = inst.fields.dup; + this.constructor = null; + if (this.fields) bindMethods(); } Variant get(TokenI name) { if (auto field = name.lexeme in fields) { - if (field.convertsTo!(Fun)) { - return Variant(field.get!(Fun).bind(this)); - } return *field; } throw new RuntimeError(name, @@ -36,4 +37,31 @@ class Instance { void toString(scope void delegate(const(char)[]) sink) const { sink("<class instance>"); } + + void addFields(Variant[string] newf) { + foreach(name, value; newf.byPair) { + fields.require(name, value); + } + } + + Variant[string] getFields() { + return fields; + } + + private void bindMethods() { + foreach(name, field; fields.byPair) { + if (field.convertsTo!(Fun)) { + auto ifun = field.get!(Fun).bind(this); + if (name == "init") { + ifun.setInitializer(); + constructor = ifun; + } + fields[name] = Variant(ifun); + } + } + } + + void construct(Variant[] arguments, Interpreter interpreter) { + if(constructor) constructor.call(interpreter, arguments); + } } diff --git a/source/interpreter.d b/source/interpreter.d @@ -144,7 +144,7 @@ class Interpreter : StmtVisitor, ExprVisitor { } void visit(Class expr) { - value = new Cls(expr.methods); + value = new Cls(expr.methods, expr.classmethods, this); } void visit(Get expr) { diff --git a/source/parser.d b/source/parser.d @@ -24,7 +24,7 @@ returnStmt → "return" expression? ";" ; funDecl → "fun" IDENTIFIER function ; function → "(" parameters? ")" block ; classDecl → "class" IDENTIFIER class ; -class → "{" ( IDENTIFIER ( function | "=" expression ) )* "}" ; +class → "{" ( "class"? IDENTIFIER ( function | "=" expression ) )* "}" ; parameters → IDENTIFIER ( "," IDENTIFIER )* ; forStmt → "for" "(" ( varDecl | exprStmt | ";" ) expression? ";" @@ -119,12 +119,16 @@ class Parser { consume(TokenType.LEFT_BRACE, "Expect '{' before class body."); Var[] fields; - while (!isAtEnd() && match(TokenType.IDENTIFIER)) { + Var[] classfields; + + while (!isAtEnd() && !check(TokenType.RIGHT_BRACE)) { + Var[]* lfields = match(TokenType.CLASS) ? &classfields : &fields; + consume(TokenType.IDENTIFIER, "Expect field name."); TokenI name = previous(); if (check(TokenType.LEFT_PAREN)) { - fields ~= statement!(Var)(name, fun()); + *lfields ~= statement!(Var)(name, fun()); } else if (match(TokenType.EQUAL)) { - fields ~= statement!(Var)(name, expression()); + *lfields ~= statement!(Var)(name, expression()); } else { Lox.error(name, "Expect field declaration"); } @@ -132,7 +136,7 @@ class Parser { consume(TokenType.RIGHT_BRACE, "Expect '}' after class."); - return new Class(fields); + return new Class(fields, classfields); } private Stmt varDeclaration() { diff --git a/source/resolver.d b/source/resolver.d @@ -215,11 +215,16 @@ class Resolver : StmtVisitor, ExprVisitor { ClassType enclosingClass = currentClass; currentClass = ClassType.CLASS; beginScope(); - foreach(method; cl.methods) { + foreach(i, method; chain(cl.methods, cl.classmethods).enumerate()) { if (auto fun = cast(Function) method.initializer) { scopes.front()["this"] = VarRef(VarState.REFERENCED, 0); - FunctionType declaration = method.name.lexeme == "init" ? - FunctionType.INITIALIZER : FunctionType.METHOD; + FunctionType declaration = FunctionType.METHOD; + if (method.name.lexeme == "init") { + declaration = FunctionType.INITIALIZER; + if (i >= cl.methods.length && fun.params.length > 0) { + Lox.error(fun.params[0], "class initializer cannot have arguments"); + } + } resolveFunction(fun, declaration); } else { resolve(method.initializer);