Move classes implementation outside of parser module

This commit is contained in:
aNNiMON 2023-10-23 22:08:22 +03:00 committed by Victor Melnik
parent d9ae2c7e82
commit ec04e5faca
18 changed files with 199 additions and 161 deletions

View File

@ -6,11 +6,6 @@ public final class UnknownClassException extends OwnLangRuntimeException {
private final String className;
public UnknownClassException(String name) {
super("Unknown class " + name);
this.className = name;
}
public UnknownClassException(String name, Range range) {
super("Unknown class " + name, range);
this.className = name;

View File

@ -0,0 +1,24 @@
package com.annimon.ownlang.lib;
import java.util.List;
public record ClassDeclaration(
String name,
List<ClassField> classFields,
List<ClassMethod> classMethods) implements Instantiable {
/**
* Create an instance and put evaluated fields with method declarations
* @return new {@link ClassInstance}
*/
public ClassInstance newInstance(Value[] args) {
final var instance = new ClassInstance(name);
for (ClassField f : classFields) {
instance.addField(f);
}
for (ClassMethod m : classMethods) {
instance.addMethod(m);
}
return instance.callConstructor(args);
}
}

View File

@ -0,0 +1,7 @@
package com.annimon.ownlang.lib;
public record ClassField(
String name,
EvaluableValue evaluableValue
) {
}

View File

@ -1,16 +1,18 @@
package com.annimon.ownlang.lib;
import com.annimon.ownlang.exceptions.OwnLangRuntimeException;
import com.annimon.ownlang.exceptions.TypeException;
import java.util.Objects;
public class ClassInstanceValue implements Value {
public class ClassInstance implements Value {
private final String className;
private final MapValue thisMap;
private ClassMethod constructor;
private UserDefinedFunction toString;
private ClassMethod toString;
private boolean isInstantiated;
public ClassInstanceValue(String name) {
public ClassInstance(String name) {
this.className = name;
thisMap = new MapValue(10);
}
@ -19,31 +21,33 @@ public class ClassInstanceValue implements Value {
return thisMap;
}
public String getClassName() {
return className;
public void addField(ClassField f) {
thisMap.set(f.name(), f.evaluableValue().eval());
}
public void addField(String name, Value value) {
thisMap.set(name, value);
}
public void addMethod(String name, ClassMethod method) {
if (name.equals("toString")) {
toString = method;
}
public void addMethod(ClassMethod method) {
method.setClassInstance(this);
final String name = method.getName();
thisMap.set(name, method);
if (name.equals(className)) {
constructor = method;
} else if (name.equals("toString")) {
toString = method;
}
}
public void callConstructor(Value[] args) {
public ClassInstance callConstructor(Value[] args) {
if (isInstantiated) {
throw new OwnLangRuntimeException(
"Class %s was already instantiated".formatted(className));
}
if (constructor != null) {
CallStack.enter("class " + className, constructor, null);
constructor.execute(args);
CallStack.exit();
}
isInstantiated = true;
return this;
}
public Value access(Value value) {
@ -53,15 +57,16 @@ public class ClassInstanceValue implements Value {
public void set(Value key, Value value) {
final Value v = thisMap.get(key);
if (v == null) {
throw new RuntimeException("Unable to add new field "
+ key.asString() + " to class " + className);
throw new OwnLangRuntimeException(
"Unable to add new field %s to class %s"
.formatted(key.asString(), className));
}
thisMap.set(key, value);
}
@Override
public Object raw() {
return null;
return thisMap;
}
@Override
@ -77,9 +82,9 @@ public class ClassInstanceValue implements Value {
@Override
public String asString() {
if (toString != null) {
return toString.execute(new Value[0]).asString();
return toString.execute().asString();
}
return className + "@" + thisMap;
return className + "@" + thisMap.asString();
}
@Override
@ -100,7 +105,7 @@ public class ClassInstanceValue implements Value {
if (obj == null) return false;
if (getClass() != obj.getClass())
return false;
final ClassInstanceValue other = (ClassInstanceValue) obj;
final ClassInstance other = (ClassInstance) obj;
return Objects.equals(this.className, other.className)
&& Objects.equals(this.thisMap, other.thisMap);
}

View File

@ -0,0 +1,60 @@
package com.annimon.ownlang.lib;
import java.util.Objects;
public class ClassMethod implements Function {
private final String name;
private final Function function;
private ClassInstance classInstance;
public ClassMethod(String name, Function function) {
this.name = name;
this.function = function;
}
public String getName() {
return name;
}
public void setClassInstance(ClassInstance classInstance) {
this.classInstance = classInstance;
}
@Override
public Value execute(Value... args) {
ScopeHandler.push();
ScopeHandler.defineVariableInCurrentScope("this", classInstance.getThisMap());
try {
return function.execute(args);
} finally {
ScopeHandler.pop();
}
}
@Override
public int getArgsCount() {
return function.getArgsCount();
}
@Override
public boolean equals(Object obj) {
if (obj == this) return true;
if (obj == null || obj.getClass() != this.getClass()) return false;
var that = (ClassMethod) obj;
return Objects.equals(this.name, that.name) &&
Objects.equals(this.function, that.function);
}
@Override
public int hashCode() {
return Objects.hash(name, function);
}
@Override
public String toString() {
return "ClassMethod[" +
"name=" + name + ", " +
"function=" + function + ']';
}
}

View File

@ -0,0 +1,6 @@
package com.annimon.ownlang.lib;
public interface EvaluableValue {
Value eval();
}

View File

@ -8,11 +8,13 @@ import java.util.concurrent.CopyOnWriteArraySet;
final class RootScope extends Scope {
private final Map<String, Value> constants;
private final Map<String, Function> functions;
private final Map<String, ClassDeclaration> classDeclarations;
private final Set<String> loadedModules;
RootScope() {
functions = new ConcurrentHashMap<>();
constants = new ConcurrentHashMap<>();
classDeclarations = new ConcurrentHashMap<>();
constants.put("true", NumberValue.ONE);
constants.put("false", NumberValue.ZERO);
loadedModules = new CopyOnWriteArraySet<>();
@ -71,6 +73,18 @@ final class RootScope extends Scope {
}
public ClassDeclaration getClassDeclaration(String name) {
return classDeclarations.get(name);
}
public void setClassDeclaration(ClassDeclaration classDeclaration) {
classDeclarations.put(classDeclaration.name(), classDeclaration);
}
public Map<String, ClassDeclaration> getClassDeclarations() {
return classDeclarations;
}
public Set<String> getLoadedModules() {
return loadedModules;
}

View File

@ -28,6 +28,11 @@ public final class ScopeHandler {
return rootScope.getFunctions();
}
public static Map<String, ClassDeclaration> classDeclarations() {
return rootScope.getClassDeclarations();
}
static RootScope rootScope() {
return rootScope;
}
@ -75,6 +80,15 @@ public final class ScopeHandler {
}
public static ClassDeclaration getClassDeclaration(String name) {
return rootScope.getClassDeclaration(name);
}
public static void setClassDeclaration(ClassDeclaration classDeclaration) {
rootScope.setClassDeclaration(classDeclaration);
}
public static boolean isVariableOrConstantExists(String name) {
if (rootScope().containsConstant(name)) {
return true;

View File

@ -1,31 +0,0 @@
package com.annimon.ownlang.lib;
import com.annimon.ownlang.parser.ast.ClassDeclarationStatement;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public final class ClassDeclarations {
private static final Map<String, ClassDeclarationStatement> declarations;
static {
declarations = new ConcurrentHashMap<>();
}
private ClassDeclarations() { }
public static void clear() {
declarations.clear();
}
public static Map<String, ClassDeclarationStatement> getAll() {
return declarations;
}
public static ClassDeclarationStatement get(String key) {
return declarations.get(key);
}
public static void set(String key, ClassDeclarationStatement classDef) {
declarations.put(key, classDef);
}
}

View File

@ -1,27 +0,0 @@
package com.annimon.ownlang.lib;
import com.annimon.ownlang.parser.ast.Arguments;
import com.annimon.ownlang.parser.ast.Statement;
import com.annimon.ownlang.util.Range;
public class ClassMethod extends UserDefinedFunction {
public final ClassInstanceValue classInstance;
public ClassMethod(Arguments arguments, Statement body, ClassInstanceValue classInstance, Range range) {
super(arguments, body, range);
this.classInstance = classInstance;
}
@Override
public Value execute(Value[] values) {
ScopeHandler.push();
ScopeHandler.defineVariableInCurrentScope("this", classInstance.getThisMap());
try {
return super.execute(values);
} finally {
ScopeHandler.pop();
}
}
}

View File

@ -1,36 +0,0 @@
package com.annimon.ownlang.lib;
import com.annimon.ownlang.exceptions.UnknownClassException;
import java.util.HashMap;
import java.util.Map;
public final class Classes {
private static final Map<String, ClassInstanceValue> classes;
static {
classes = new HashMap<>();
}
private Classes() { }
public static void clear() {
classes.clear();
}
public static Map<String, ClassInstanceValue> getFunctions() {
return classes;
}
public static boolean isExists(String key) {
return classes.containsKey(key);
}
public static ClassInstanceValue get(String key) {
if (!isExists(key)) throw new UnknownClassException(key);
return classes.get(key);
}
public static void set(String key, ClassInstanceValue classDef) {
classes.put(key, classDef);
}
}

View File

@ -815,7 +815,8 @@ public final class Parser {
final var startTokenIndex = index - 1;
final Arguments arguments = arguments();
final Statement statement = statementBody();
return new ValueExpression(new UserDefinedFunction(arguments, statement, getRange(startTokenIndex, index - 1)));
final Range range = getRange(startTokenIndex, index - 1);
return new ValueExpression(new UserDefinedFunction(arguments, statement, range));
}
return variable();
}

View File

@ -1,12 +1,13 @@
package com.annimon.ownlang.parser.ast;
import com.annimon.ownlang.lib.EvaluableValue;
import com.annimon.ownlang.lib.Value;
/**
*
* @author aNNiMON
*/
public final class AssignmentExpression extends InterruptableNode implements Statement {
public final class AssignmentExpression extends InterruptableNode implements Statement, EvaluableValue {
public final Accessible target;
public final BinaryExpression.Operator operation;

View File

@ -1,8 +1,6 @@
package com.annimon.ownlang.parser.ast;
import com.annimon.ownlang.lib.ClassDeclarations;
import com.annimon.ownlang.lib.NumberValue;
import com.annimon.ownlang.lib.Value;
import com.annimon.ownlang.lib.*;
import java.util.ArrayList;
import java.util.List;
@ -28,10 +26,28 @@ public final class ClassDeclarationStatement implements Statement {
@Override
public Value eval() {
ClassDeclarations.set(name, this);
final var classFields = fields.stream()
.map(this::toClassField)
.toList();
final var classMethods = methods.stream()
.map(this::toClassMethod)
.toList();
final var declaration = new ClassDeclaration(name, classFields, classMethods);
ScopeHandler.setClassDeclaration(declaration);
return NumberValue.ZERO;
}
private ClassField toClassField(AssignmentExpression f) {
// TODO check only variable assignments
final String fieldName = ((VariableExpression) f.target).name;
return new ClassField(fieldName, f);
}
private ClassMethod toClassMethod(FunctionDefineStatement m) {
final var function = new UserDefinedFunction(m.arguments, m.body, m.getRange());
return new ClassMethod(m.name, function);
}
@Override
public void accept(Visitor visitor) {
visitor.visit(this);

View File

@ -50,7 +50,7 @@ public final class ContainerAccessExpression implements Node, Accessible {
case Types.ARRAY -> ((ArrayValue) container).get(lastIndex);
case Types.MAP -> ((MapValue) container).get(lastIndex);
case Types.STRING -> ((StringValue) container).access(lastIndex);
case Types.CLASS -> ((ClassInstanceValue) container).access(lastIndex);
case Types.CLASS -> ((ClassInstance) container).access(lastIndex);
default -> throw new TypeException("Array or map expected. Got " + Types.typeToString(container.type()));
};
}
@ -62,7 +62,7 @@ public final class ContainerAccessExpression implements Node, Accessible {
switch (container.type()) {
case Types.ARRAY -> ((ArrayValue) container).set(lastIndex.asInt(), value);
case Types.MAP -> ((MapValue) container).set(lastIndex, value);
case Types.CLASS -> ((ClassInstanceValue) container).set(lastIndex, value);
case Types.CLASS -> ((ClassInstance) container).set(lastIndex, value);
default -> throw new TypeException("Array or map expected. Got " + container.type());
}
return value;

View File

@ -48,8 +48,8 @@ public final class FunctionDefineStatement implements Statement, SourceLocation
@Override
public String toString() {
if (body instanceof ReturnStatement) {
return String.format("def %s%s = %s", name, arguments, ((ReturnStatement)body).expression);
if (body instanceof ReturnStatement rs) {
return String.format("def %s%s = %s", name, arguments, rs.expression);
}
return String.format("def %s%s %s", name, arguments, body);
}

View File

@ -29,41 +29,29 @@ public final class ObjectCreationExpression implements Node, SourceLocation {
@Override
public Value eval() {
final ClassDeclarationStatement cd = ClassDeclarations.get(className);
if (cd == null) {
final ClassDeclaration cd = ScopeHandler.getClassDeclaration(className);
if (cd != null) {
return cd.newInstance(constructorArgs());
}
// Is Instantiable?
if (ScopeHandler.isVariableOrConstantExists(className)) {
final Value variable = ScopeHandler.getVariableOrConstant(className);
if (variable instanceof Instantiable instantiable) {
return instantiable.newInstance(ctorArgs());
return instantiable.newInstance(constructorArgs());
}
}
throw new UnknownClassException(className, range);
}
// Create an instance and put evaluated fields with method declarations
final ClassInstanceValue instance = new ClassInstanceValue(className);
for (AssignmentExpression f : cd.fields) {
// TODO check only variable assignments
final String fieldName = ((VariableExpression) f.target).name;
instance.addField(fieldName, f.eval());
}
for (FunctionDefineStatement m : cd.methods) {
instance.addMethod(m.name, new ClassMethod(m.arguments, m.body, instance, m.getRange()));
}
// Call a constructor
instance.callConstructor(ctorArgs());
return instance;
}
private Value[] ctorArgs() {
private Value[] constructorArgs() {
final int argsSize = constructorArguments.size();
final Value[] ctorArgs = new Value[argsSize];
for (int i = 0; i < argsSize; i++) {
ctorArgs[i] = constructorArguments.get(i).eval();
final Value[] args = new Value[argsSize];
int i = 0;
for (Node argument : constructorArguments) {
args[i++] = argument.eval();
}
return ctorArgs;
return args;
}
@Override

View File

@ -75,6 +75,7 @@ public abstract class OptimizationVisitor<T> implements ResultVisitor<Node, T> {
@Override
public Node visit(ClassDeclarationStatement s, T t) {
// TODO fields and methods
return s;
}