diff --git a/ownlang-core/src/main/java/com/annimon/ownlang/exceptions/UnknownClassException.java b/ownlang-core/src/main/java/com/annimon/ownlang/exceptions/UnknownClassException.java index 8c2bed3..d963c25 100644 --- a/ownlang-core/src/main/java/com/annimon/ownlang/exceptions/UnknownClassException.java +++ b/ownlang-core/src/main/java/com/annimon/ownlang/exceptions/UnknownClassException.java @@ -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; diff --git a/ownlang-core/src/main/java/com/annimon/ownlang/lib/ClassDeclaration.java b/ownlang-core/src/main/java/com/annimon/ownlang/lib/ClassDeclaration.java new file mode 100644 index 0000000..6f0e50e --- /dev/null +++ b/ownlang-core/src/main/java/com/annimon/ownlang/lib/ClassDeclaration.java @@ -0,0 +1,24 @@ +package com.annimon.ownlang.lib; + +import java.util.List; + +public record ClassDeclaration( + String name, + List classFields, + List 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); + } +} diff --git a/ownlang-core/src/main/java/com/annimon/ownlang/lib/ClassField.java b/ownlang-core/src/main/java/com/annimon/ownlang/lib/ClassField.java new file mode 100644 index 0000000..469c6b0 --- /dev/null +++ b/ownlang-core/src/main/java/com/annimon/ownlang/lib/ClassField.java @@ -0,0 +1,7 @@ +package com.annimon.ownlang.lib; + +public record ClassField( + String name, + EvaluableValue evaluableValue +) { +} diff --git a/ownlang-parser/src/main/java/com/annimon/ownlang/lib/ClassInstanceValue.java b/ownlang-core/src/main/java/com/annimon/ownlang/lib/ClassInstance.java similarity index 65% rename from ownlang-parser/src/main/java/com/annimon/ownlang/lib/ClassInstanceValue.java rename to ownlang-core/src/main/java/com/annimon/ownlang/lib/ClassInstance.java index 3def100..75c1292 100644 --- a/ownlang-parser/src/main/java/com/annimon/ownlang/lib/ClassInstanceValue.java +++ b/ownlang-core/src/main/java/com/annimon/ownlang/lib/ClassInstance.java @@ -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); } diff --git a/ownlang-core/src/main/java/com/annimon/ownlang/lib/ClassMethod.java b/ownlang-core/src/main/java/com/annimon/ownlang/lib/ClassMethod.java new file mode 100644 index 0000000..643b700 --- /dev/null +++ b/ownlang-core/src/main/java/com/annimon/ownlang/lib/ClassMethod.java @@ -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 + ']'; + } +} diff --git a/ownlang-core/src/main/java/com/annimon/ownlang/lib/EvaluableValue.java b/ownlang-core/src/main/java/com/annimon/ownlang/lib/EvaluableValue.java new file mode 100644 index 0000000..bc90ba0 --- /dev/null +++ b/ownlang-core/src/main/java/com/annimon/ownlang/lib/EvaluableValue.java @@ -0,0 +1,6 @@ +package com.annimon.ownlang.lib; + +public interface EvaluableValue { + + Value eval(); +} diff --git a/ownlang-core/src/main/java/com/annimon/ownlang/lib/RootScope.java b/ownlang-core/src/main/java/com/annimon/ownlang/lib/RootScope.java index 0cdcd81..19682bf 100644 --- a/ownlang-core/src/main/java/com/annimon/ownlang/lib/RootScope.java +++ b/ownlang-core/src/main/java/com/annimon/ownlang/lib/RootScope.java @@ -8,11 +8,13 @@ import java.util.concurrent.CopyOnWriteArraySet; final class RootScope extends Scope { private final Map constants; private final Map functions; + private final Map classDeclarations; private final Set 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 getClassDeclarations() { + return classDeclarations; + } + public Set getLoadedModules() { return loadedModules; } diff --git a/ownlang-core/src/main/java/com/annimon/ownlang/lib/ScopeHandler.java b/ownlang-core/src/main/java/com/annimon/ownlang/lib/ScopeHandler.java index a61d6ca..f82af08 100644 --- a/ownlang-core/src/main/java/com/annimon/ownlang/lib/ScopeHandler.java +++ b/ownlang-core/src/main/java/com/annimon/ownlang/lib/ScopeHandler.java @@ -28,6 +28,11 @@ public final class ScopeHandler { return rootScope.getFunctions(); } + public static Map 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; diff --git a/ownlang-parser/src/main/java/com/annimon/ownlang/lib/ClassDeclarations.java b/ownlang-parser/src/main/java/com/annimon/ownlang/lib/ClassDeclarations.java deleted file mode 100644 index b079ef3..0000000 --- a/ownlang-parser/src/main/java/com/annimon/ownlang/lib/ClassDeclarations.java +++ /dev/null @@ -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 declarations; - static { - declarations = new ConcurrentHashMap<>(); - } - - private ClassDeclarations() { } - - public static void clear() { - declarations.clear(); - } - - public static Map 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); - } -} diff --git a/ownlang-parser/src/main/java/com/annimon/ownlang/lib/ClassMethod.java b/ownlang-parser/src/main/java/com/annimon/ownlang/lib/ClassMethod.java deleted file mode 100644 index 5ad723e..0000000 --- a/ownlang-parser/src/main/java/com/annimon/ownlang/lib/ClassMethod.java +++ /dev/null @@ -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(); - } - } -} diff --git a/ownlang-parser/src/main/java/com/annimon/ownlang/lib/Classes.java b/ownlang-parser/src/main/java/com/annimon/ownlang/lib/Classes.java deleted file mode 100644 index 488d546..0000000 --- a/ownlang-parser/src/main/java/com/annimon/ownlang/lib/Classes.java +++ /dev/null @@ -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 classes; - static { - classes = new HashMap<>(); - } - - private Classes() { } - - public static void clear() { - classes.clear(); - } - - public static Map 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); - } -} diff --git a/ownlang-parser/src/main/java/com/annimon/ownlang/parser/Parser.java b/ownlang-parser/src/main/java/com/annimon/ownlang/parser/Parser.java index 278dee5..1c8417f 100644 --- a/ownlang-parser/src/main/java/com/annimon/ownlang/parser/Parser.java +++ b/ownlang-parser/src/main/java/com/annimon/ownlang/parser/Parser.java @@ -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(); } diff --git a/ownlang-parser/src/main/java/com/annimon/ownlang/parser/ast/AssignmentExpression.java b/ownlang-parser/src/main/java/com/annimon/ownlang/parser/ast/AssignmentExpression.java index a698284..e76c0af 100644 --- a/ownlang-parser/src/main/java/com/annimon/ownlang/parser/ast/AssignmentExpression.java +++ b/ownlang-parser/src/main/java/com/annimon/ownlang/parser/ast/AssignmentExpression.java @@ -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; diff --git a/ownlang-parser/src/main/java/com/annimon/ownlang/parser/ast/ClassDeclarationStatement.java b/ownlang-parser/src/main/java/com/annimon/ownlang/parser/ast/ClassDeclarationStatement.java index b8f3e94..482cfba 100644 --- a/ownlang-parser/src/main/java/com/annimon/ownlang/parser/ast/ClassDeclarationStatement.java +++ b/ownlang-parser/src/main/java/com/annimon/ownlang/parser/ast/ClassDeclarationStatement.java @@ -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,9 +26,27 @@ 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) { diff --git a/ownlang-parser/src/main/java/com/annimon/ownlang/parser/ast/ContainerAccessExpression.java b/ownlang-parser/src/main/java/com/annimon/ownlang/parser/ast/ContainerAccessExpression.java index b60c19c..09b3f99 100644 --- a/ownlang-parser/src/main/java/com/annimon/ownlang/parser/ast/ContainerAccessExpression.java +++ b/ownlang-parser/src/main/java/com/annimon/ownlang/parser/ast/ContainerAccessExpression.java @@ -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; diff --git a/ownlang-parser/src/main/java/com/annimon/ownlang/parser/ast/FunctionDefineStatement.java b/ownlang-parser/src/main/java/com/annimon/ownlang/parser/ast/FunctionDefineStatement.java index f812756..0876802 100644 --- a/ownlang-parser/src/main/java/com/annimon/ownlang/parser/ast/FunctionDefineStatement.java +++ b/ownlang-parser/src/main/java/com/annimon/ownlang/parser/ast/FunctionDefineStatement.java @@ -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); } diff --git a/ownlang-parser/src/main/java/com/annimon/ownlang/parser/ast/ObjectCreationExpression.java b/ownlang-parser/src/main/java/com/annimon/ownlang/parser/ast/ObjectCreationExpression.java index 5204f3d..a19bbf6 100644 --- a/ownlang-parser/src/main/java/com/annimon/ownlang/parser/ast/ObjectCreationExpression.java +++ b/ownlang-parser/src/main/java/com/annimon/ownlang/parser/ast/ObjectCreationExpression.java @@ -29,41 +29,29 @@ public final class ObjectCreationExpression implements Node, SourceLocation { @Override public Value eval() { - final ClassDeclarationStatement cd = ClassDeclarations.get(className); - if (cd == null) { - // Is Instantiable? - if (ScopeHandler.isVariableOrConstantExists(className)) { - final Value variable = ScopeHandler.getVariableOrConstant(className); - if (variable instanceof Instantiable instantiable) { - return instantiable.newInstance(ctorArgs()); - } + 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(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; + throw new UnknownClassException(className, range); } - - 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 diff --git a/ownlang-parser/src/main/java/com/annimon/ownlang/parser/optimization/OptimizationVisitor.java b/ownlang-parser/src/main/java/com/annimon/ownlang/parser/optimization/OptimizationVisitor.java index 4a42c70..fee1831 100644 --- a/ownlang-parser/src/main/java/com/annimon/ownlang/parser/optimization/OptimizationVisitor.java +++ b/ownlang-parser/src/main/java/com/annimon/ownlang/parser/optimization/OptimizationVisitor.java @@ -75,6 +75,7 @@ public abstract class OptimizationVisitor implements ResultVisitor { @Override public Node visit(ClassDeclarationStatement s, T t) { + // TODO fields and methods return s; }