From e6f463257720bee91a1b4954d97684dbb3398cbf Mon Sep 17 00:00:00 2001 From: Michael Williamson Date: Tue, 6 Oct 2015 16:24:37 +0100 Subject: Add support for constructors and fields --- .../couscous/ast/AssignableExpressionNode.java | 7 ++ .../org/zwobble/couscous/ast/AssignmentNode.java | 14 ++- .../org/zwobble/couscous/ast/CallableNode.java | 9 ++ .../java/org/zwobble/couscous/ast/ClassNode.java | 26 +----- .../org/zwobble/couscous/ast/ClassNodeBuilder.java | 103 +++++++++++++++++++++ .../org/zwobble/couscous/ast/ConstructorNode.java | 18 ++++ .../org/zwobble/couscous/ast/ExpressionNode.java | 12 +++ .../org/zwobble/couscous/ast/FieldAccessNode.java | 23 +++++ .../zwobble/couscous/ast/FieldDeclarationNode.java | 9 ++ .../java/org/zwobble/couscous/ast/MethodNode.java | 2 +- .../zwobble/couscous/ast/ThisReferenceNode.java | 15 +++ .../couscous/ast/VariableReferenceNode.java | 8 +- .../visitors/AssignableExpressionNodeVisitor.java | 9 ++ .../ast/visitors/ExpressionNodeMapper.java | 4 + .../zwobble/couscous/ast/visitors/NodeVisitor.java | 6 ++ .../ast/visitors/NodeVisitorWithEmptyDefaults.java | 15 +++ .../couscous/ast/visitors/NodeVisitors.java | 23 ++++- .../backends/python/PythonCodeGenerator.java | 58 ++++++++++-- .../backends/python/ast/PythonAssignmentNode.java | 2 +- .../zwobble/couscous/interpreter/Environment.java | 15 ++- .../zwobble/couscous/interpreter/Evaluator.java | 34 ++++++- .../org/zwobble/couscous/interpreter/Executor.java | 20 ++-- .../zwobble/couscous/interpreter/Interpreter.java | 3 +- .../zwobble/couscous/interpreter/NoSuchField.java | 12 +++ .../values/BooleanInterpreterValue.java | 6 ++ .../couscous/interpreter/values/ConcreteType.java | 66 ++++++++++--- .../values/IntegerInterpreterValue.java | 6 ++ .../interpreter/values/InterpreterValue.java | 1 + .../interpreter/values/ObjectInterpreterValue.java | 17 ++++ .../interpreter/values/StringInterpreterValue.java | 6 ++ .../interpreter/values/UnitInterpreterValue.java | 6 ++ 31 files changed, 493 insertions(+), 62 deletions(-) create mode 100644 src/main/java/org/zwobble/couscous/ast/AssignableExpressionNode.java create mode 100644 src/main/java/org/zwobble/couscous/ast/CallableNode.java create mode 100644 src/main/java/org/zwobble/couscous/ast/ClassNodeBuilder.java create mode 100644 src/main/java/org/zwobble/couscous/ast/ConstructorNode.java create mode 100644 src/main/java/org/zwobble/couscous/ast/FieldAccessNode.java create mode 100644 src/main/java/org/zwobble/couscous/ast/FieldDeclarationNode.java create mode 100644 src/main/java/org/zwobble/couscous/ast/ThisReferenceNode.java create mode 100644 src/main/java/org/zwobble/couscous/ast/visitors/AssignableExpressionNodeVisitor.java create mode 100644 src/main/java/org/zwobble/couscous/interpreter/NoSuchField.java (limited to 'src/main/java') diff --git a/src/main/java/org/zwobble/couscous/ast/AssignableExpressionNode.java b/src/main/java/org/zwobble/couscous/ast/AssignableExpressionNode.java new file mode 100644 index 0000000..eba641f --- /dev/null +++ b/src/main/java/org/zwobble/couscous/ast/AssignableExpressionNode.java @@ -0,0 +1,7 @@ +package org.zwobble.couscous.ast; + +import org.zwobble.couscous.ast.visitors.AssignableExpressionNodeVisitor; + +public interface AssignableExpressionNode extends ExpressionNode { + void accept(AssignableExpressionNodeVisitor mapper); +} diff --git a/src/main/java/org/zwobble/couscous/ast/AssignmentNode.java b/src/main/java/org/zwobble/couscous/ast/AssignmentNode.java index d7ea09f..483d4c2 100644 --- a/src/main/java/org/zwobble/couscous/ast/AssignmentNode.java +++ b/src/main/java/org/zwobble/couscous/ast/AssignmentNode.java @@ -8,11 +8,23 @@ import lombok.Value; @Value public class AssignmentNode implements ExpressionNode { + public static StatementNode assignStatement(AssignableExpressionNode target, ExpressionNode value) { + return new ExpressionStatementNode(assign(target, value)); + } + + public static AssignmentNode assign(AssignableExpressionNode target, ExpressionNode value) { + return new AssignmentNode(target, value); + } + + public static StatementNode assignStatement(VariableNode target, ExpressionNode value) { + return new ExpressionStatementNode(assign(target, value)); + } + public static AssignmentNode assign(VariableNode target, ExpressionNode value) { return new AssignmentNode(reference(target), value); } - VariableReferenceNode target; + AssignableExpressionNode target; ExpressionNode value; @Override diff --git a/src/main/java/org/zwobble/couscous/ast/CallableNode.java b/src/main/java/org/zwobble/couscous/ast/CallableNode.java new file mode 100644 index 0000000..79d2e84 --- /dev/null +++ b/src/main/java/org/zwobble/couscous/ast/CallableNode.java @@ -0,0 +1,9 @@ +package org.zwobble.couscous.ast; + +import java.util.List; + +public interface CallableNode extends Node { + List getArguments(); + List getBody(); + +} diff --git a/src/main/java/org/zwobble/couscous/ast/ClassNode.java b/src/main/java/org/zwobble/couscous/ast/ClassNode.java index 5f08d38..280b772 100644 --- a/src/main/java/org/zwobble/couscous/ast/ClassNode.java +++ b/src/main/java/org/zwobble/couscous/ast/ClassNode.java @@ -4,37 +4,17 @@ import java.util.List; import org.zwobble.couscous.ast.visitors.NodeVisitor; -import com.google.common.collect.ImmutableList; - import lombok.Singular; import lombok.Value; @Value public class ClassNode implements Node { - public static ClassNode.Builder builder(String name) { - return new Builder(name); - } - - public static class Builder { - private final String name; - private final ImmutableList.Builder methods; - - public Builder(String name) { - this.name = name; - this.methods = ImmutableList.builder(); - } - - public Builder method(MethodNode method) { - this.methods.add(method); - return this; - } - - public ClassNode build() { - return new ClassNode(TypeName.of(name), methods.build()); - } + public static ClassNodeBuilder builder(String name) { + return new ClassNodeBuilder(name); } TypeName name; + ConstructorNode constructor; @Singular List methods; diff --git a/src/main/java/org/zwobble/couscous/ast/ClassNodeBuilder.java b/src/main/java/org/zwobble/couscous/ast/ClassNodeBuilder.java new file mode 100644 index 0000000..cd324b9 --- /dev/null +++ b/src/main/java/org/zwobble/couscous/ast/ClassNodeBuilder.java @@ -0,0 +1,103 @@ +package org.zwobble.couscous.ast; + +import java.util.Optional; +import java.util.function.Function; + +import com.google.common.collect.ImmutableList; + +public class ClassNodeBuilder { + private final TypeName name; + private final ImmutableList.Builder fields; + private Optional constructor; + private final ImmutableList.Builder methods; + + public ClassNodeBuilder(String name) { + this.name = TypeName.of(name); + this.fields = ImmutableList.builder(); + this.constructor = Optional.empty(); + this.methods = ImmutableList.builder(); + } + + public ClassNodeBuilder field(String name, TypeName type) { + this.fields.add(FieldDeclarationNode.field(name, type)); + return this; + } + + public ClassNodeBuilder constructor(Function build) { + this.constructor = Optional.of(build.apply(new ConstructorBuilder()).build()); + return this; + } + + public ClassNodeBuilder method(MethodNode method) { + this.methods.add(method); + return this; + } + + public ClassNodeBuilder method(String name, Function build) { + this.methods.add(build.apply(new MethodBuilder(name)).build()); + return this; + } + + public ClassNode build() { + return new ClassNode( + name, + constructor.orElse(new ConstructorBuilder().build()), + methods.build()); + } + + public class ConstructorBuilder { + private final ImmutableList.Builder arguments; + private final ImmutableList.Builder statements; + + public ConstructorBuilder() { + this.arguments = ImmutableList.builder(); + this.statements = ImmutableList.builder(); + } + + public ThisReferenceNode thisReference() { + return ThisReferenceNode.thisReference(name); + } + + public ConstructorBuilder argument(FormalArgumentNode argument) { + arguments.add(argument); + return this; + } + + public ConstructorBuilder statement(StatementNode statement) { + statements.add(statement); + return this; + } + + public ConstructorNode build() { + return ConstructorNode.constructor( + arguments.build(), + statements.build()); + } + } + + public class MethodBuilder { + private final String methodName; + private final ImmutableList.Builder statements; + + public MethodBuilder(String name) { + this.methodName = name; + this.statements = ImmutableList.builder(); + } + + public ThisReferenceNode thisReference() { + return ThisReferenceNode.thisReference(name); + } + + public MethodBuilder statement(StatementNode statement) { + statements.add(statement); + return this; + } + + public MethodNode build() { + return MethodNode.builder() + .name(methodName) + .body(statements.build()) + .build(); + } + } +} \ No newline at end of file diff --git a/src/main/java/org/zwobble/couscous/ast/ConstructorNode.java b/src/main/java/org/zwobble/couscous/ast/ConstructorNode.java new file mode 100644 index 0000000..468ef9e --- /dev/null +++ b/src/main/java/org/zwobble/couscous/ast/ConstructorNode.java @@ -0,0 +1,18 @@ +package org.zwobble.couscous.ast; + +import java.util.List; + +import org.zwobble.couscous.ast.visitors.NodeVisitor; + +import lombok.Value; + +@Value(staticConstructor="constructor") +public class ConstructorNode implements CallableNode { + List arguments; + List body; + + @Override + public void accept(NodeVisitor visitor) { + visitor.visit(this); + } +} diff --git a/src/main/java/org/zwobble/couscous/ast/ExpressionNode.java b/src/main/java/org/zwobble/couscous/ast/ExpressionNode.java index 2e1cded..8210ae1 100644 --- a/src/main/java/org/zwobble/couscous/ast/ExpressionNode.java +++ b/src/main/java/org/zwobble/couscous/ast/ExpressionNode.java @@ -22,6 +22,12 @@ public interface ExpressionNode extends Node { return null; } + @Override + public Void visit(ThisReferenceNode reference) { + visitor.visit(reference); + return null; + } + @Override public Void visit(AssignmentNode assignment) { visitor.visit(assignment); @@ -51,6 +57,12 @@ public interface ExpressionNode extends Node { visitor.visit(call); return null; } + + @Override + public Void visit(FieldAccessNode fieldAccess) { + visitor.visit(fieldAccess); + return null; + } }); } } diff --git a/src/main/java/org/zwobble/couscous/ast/FieldAccessNode.java b/src/main/java/org/zwobble/couscous/ast/FieldAccessNode.java new file mode 100644 index 0000000..8de1323 --- /dev/null +++ b/src/main/java/org/zwobble/couscous/ast/FieldAccessNode.java @@ -0,0 +1,23 @@ +package org.zwobble.couscous.ast; + +import org.zwobble.couscous.ast.visitors.AssignableExpressionNodeVisitor; +import org.zwobble.couscous.ast.visitors.ExpressionNodeMapper; + +import lombok.Value; + +@Value(staticConstructor="fieldAccess") +public class FieldAccessNode implements AssignableExpressionNode { + ExpressionNode left; + String fieldName; + TypeName type; + + @Override + public T accept(ExpressionNodeMapper visitor) { + return visitor.visit(this); + } + + @Override + public void accept(AssignableExpressionNodeVisitor mapper) { + mapper.visit(this); + } +} diff --git a/src/main/java/org/zwobble/couscous/ast/FieldDeclarationNode.java b/src/main/java/org/zwobble/couscous/ast/FieldDeclarationNode.java new file mode 100644 index 0000000..49a45d3 --- /dev/null +++ b/src/main/java/org/zwobble/couscous/ast/FieldDeclarationNode.java @@ -0,0 +1,9 @@ +package org.zwobble.couscous.ast; + +import lombok.Value; + +@Value(staticConstructor="field") +public class FieldDeclarationNode { + String name; + TypeName type; +} diff --git a/src/main/java/org/zwobble/couscous/ast/MethodNode.java b/src/main/java/org/zwobble/couscous/ast/MethodNode.java index b547c92..700e9ec 100644 --- a/src/main/java/org/zwobble/couscous/ast/MethodNode.java +++ b/src/main/java/org/zwobble/couscous/ast/MethodNode.java @@ -10,7 +10,7 @@ import lombok.Value; @Builder @Value -public class MethodNode implements Node { +public class MethodNode implements CallableNode { public static MethodNodeBuilder staticMethod(String name) { return builder().isStatic(true).name(name); } diff --git a/src/main/java/org/zwobble/couscous/ast/ThisReferenceNode.java b/src/main/java/org/zwobble/couscous/ast/ThisReferenceNode.java new file mode 100644 index 0000000..c8fa54a --- /dev/null +++ b/src/main/java/org/zwobble/couscous/ast/ThisReferenceNode.java @@ -0,0 +1,15 @@ +package org.zwobble.couscous.ast; + +import org.zwobble.couscous.ast.visitors.ExpressionNodeMapper; + +import lombok.Value; + +@Value(staticConstructor="thisReference") +public class ThisReferenceNode implements ExpressionNode { + TypeName type; + + @Override + public T accept(ExpressionNodeMapper visitor) { + return visitor.visit(this); + } +} diff --git a/src/main/java/org/zwobble/couscous/ast/VariableReferenceNode.java b/src/main/java/org/zwobble/couscous/ast/VariableReferenceNode.java index a07006e..ebe79b0 100644 --- a/src/main/java/org/zwobble/couscous/ast/VariableReferenceNode.java +++ b/src/main/java/org/zwobble/couscous/ast/VariableReferenceNode.java @@ -1,11 +1,12 @@ package org.zwobble.couscous.ast; +import org.zwobble.couscous.ast.visitors.AssignableExpressionNodeVisitor; import org.zwobble.couscous.ast.visitors.ExpressionNodeMapper; import lombok.Value; @Value -public class VariableReferenceNode implements ExpressionNode { +public class VariableReferenceNode implements AssignableExpressionNode { public static VariableReferenceNode reference(VariableNode variable) { return new VariableReferenceNode(variable.getDeclaration()); } @@ -25,4 +26,9 @@ public class VariableReferenceNode implements ExpressionNode { public TypeName getType() { return referent.getType(); } + + @Override + public void accept(AssignableExpressionNodeVisitor mapper) { + mapper.visit(this); + } } diff --git a/src/main/java/org/zwobble/couscous/ast/visitors/AssignableExpressionNodeVisitor.java b/src/main/java/org/zwobble/couscous/ast/visitors/AssignableExpressionNodeVisitor.java new file mode 100644 index 0000000..57cb105 --- /dev/null +++ b/src/main/java/org/zwobble/couscous/ast/visitors/AssignableExpressionNodeVisitor.java @@ -0,0 +1,9 @@ +package org.zwobble.couscous.ast.visitors; + +import org.zwobble.couscous.ast.FieldAccessNode; +import org.zwobble.couscous.ast.VariableReferenceNode; + +public interface AssignableExpressionNodeVisitor { + void visit(VariableReferenceNode reference); + void visit(FieldAccessNode fieldAccess); +} diff --git a/src/main/java/org/zwobble/couscous/ast/visitors/ExpressionNodeMapper.java b/src/main/java/org/zwobble/couscous/ast/visitors/ExpressionNodeMapper.java index f924797..c1c3e88 100644 --- a/src/main/java/org/zwobble/couscous/ast/visitors/ExpressionNodeMapper.java +++ b/src/main/java/org/zwobble/couscous/ast/visitors/ExpressionNodeMapper.java @@ -2,18 +2,22 @@ package org.zwobble.couscous.ast.visitors; import org.zwobble.couscous.ast.AssignmentNode; import org.zwobble.couscous.ast.ConstructorCallNode; +import org.zwobble.couscous.ast.FieldAccessNode; import org.zwobble.couscous.ast.LiteralNode; import org.zwobble.couscous.ast.MethodCallNode; import org.zwobble.couscous.ast.StaticMethodCallNode; import org.zwobble.couscous.ast.TernaryConditionalNode; +import org.zwobble.couscous.ast.ThisReferenceNode; import org.zwobble.couscous.ast.VariableReferenceNode; public interface ExpressionNodeMapper { T visit(LiteralNode literal); T visit(VariableReferenceNode variableReference); + T visit(ThisReferenceNode reference); T visit(AssignmentNode assignment); T visit(TernaryConditionalNode ternaryConditional); T visit(MethodCallNode methodCall); T visit(StaticMethodCallNode staticMethodCall); T visit(ConstructorCallNode call); + T visit(FieldAccessNode fieldAccess); } diff --git a/src/main/java/org/zwobble/couscous/ast/visitors/NodeVisitor.java b/src/main/java/org/zwobble/couscous/ast/visitors/NodeVisitor.java index 7377986..8a88052 100644 --- a/src/main/java/org/zwobble/couscous/ast/visitors/NodeVisitor.java +++ b/src/main/java/org/zwobble/couscous/ast/visitors/NodeVisitor.java @@ -3,7 +3,9 @@ package org.zwobble.couscous.ast.visitors; import org.zwobble.couscous.ast.AssignmentNode; import org.zwobble.couscous.ast.ClassNode; import org.zwobble.couscous.ast.ConstructorCallNode; +import org.zwobble.couscous.ast.ConstructorNode; import org.zwobble.couscous.ast.ExpressionStatementNode; +import org.zwobble.couscous.ast.FieldAccessNode; import org.zwobble.couscous.ast.LiteralNode; import org.zwobble.couscous.ast.LocalVariableDeclarationNode; import org.zwobble.couscous.ast.MethodCallNode; @@ -11,16 +13,19 @@ import org.zwobble.couscous.ast.MethodNode; import org.zwobble.couscous.ast.ReturnNode; import org.zwobble.couscous.ast.StaticMethodCallNode; import org.zwobble.couscous.ast.TernaryConditionalNode; +import org.zwobble.couscous.ast.ThisReferenceNode; import org.zwobble.couscous.ast.VariableReferenceNode; public interface NodeVisitor { void visit(LiteralNode literal); void visit(VariableReferenceNode variableReference); + void visit(ThisReferenceNode reference); void visit(AssignmentNode assignment); void visit(TernaryConditionalNode ternaryConditional); void visit(MethodCallNode methodCall); void visit(StaticMethodCallNode staticMethodCall); void visit(ConstructorCallNode call); + void visit(FieldAccessNode fieldAccess); void visit(ReturnNode returnNode); void visit(ExpressionStatementNode expressionStatement); @@ -28,4 +33,5 @@ public interface NodeVisitor { void visit(ClassNode classNode); void visit(MethodNode methodNode); + void visit(ConstructorNode constructorNode); } diff --git a/src/main/java/org/zwobble/couscous/ast/visitors/NodeVisitorWithEmptyDefaults.java b/src/main/java/org/zwobble/couscous/ast/visitors/NodeVisitorWithEmptyDefaults.java index ac3786b..8408b7a 100644 --- a/src/main/java/org/zwobble/couscous/ast/visitors/NodeVisitorWithEmptyDefaults.java +++ b/src/main/java/org/zwobble/couscous/ast/visitors/NodeVisitorWithEmptyDefaults.java @@ -3,7 +3,9 @@ package org.zwobble.couscous.ast.visitors; import org.zwobble.couscous.ast.AssignmentNode; import org.zwobble.couscous.ast.ClassNode; import org.zwobble.couscous.ast.ConstructorCallNode; +import org.zwobble.couscous.ast.ConstructorNode; import org.zwobble.couscous.ast.ExpressionStatementNode; +import org.zwobble.couscous.ast.FieldAccessNode; import org.zwobble.couscous.ast.LiteralNode; import org.zwobble.couscous.ast.LocalVariableDeclarationNode; import org.zwobble.couscous.ast.MethodCallNode; @@ -11,6 +13,7 @@ import org.zwobble.couscous.ast.MethodNode; import org.zwobble.couscous.ast.ReturnNode; import org.zwobble.couscous.ast.StaticMethodCallNode; import org.zwobble.couscous.ast.TernaryConditionalNode; +import org.zwobble.couscous.ast.ThisReferenceNode; import org.zwobble.couscous.ast.VariableReferenceNode; public interface NodeVisitorWithEmptyDefaults extends NodeVisitor { @@ -22,6 +25,10 @@ public interface NodeVisitorWithEmptyDefaults extends NodeVisitor { default void visit(VariableReferenceNode variableReference) { } + @Override + default void visit(ThisReferenceNode reference) { + } + @Override default void visit(AssignmentNode assignment) { } @@ -41,6 +48,10 @@ public interface NodeVisitorWithEmptyDefaults extends NodeVisitor { @Override default void visit(ConstructorCallNode call) { } + + @Override + default void visit(FieldAccessNode fieldAccess) { + } @Override default void visit(ReturnNode returnNode) { @@ -61,4 +72,8 @@ public interface NodeVisitorWithEmptyDefaults extends NodeVisitor { @Override default void visit(MethodNode methodNode) { } + + @Override + default void visit(ConstructorNode constructorNode) { + } } diff --git a/src/main/java/org/zwobble/couscous/ast/visitors/NodeVisitors.java b/src/main/java/org/zwobble/couscous/ast/visitors/NodeVisitors.java index 835d96f..bb5b054 100644 --- a/src/main/java/org/zwobble/couscous/ast/visitors/NodeVisitors.java +++ b/src/main/java/org/zwobble/couscous/ast/visitors/NodeVisitors.java @@ -3,7 +3,9 @@ package org.zwobble.couscous.ast.visitors; import org.zwobble.couscous.ast.AssignmentNode; import org.zwobble.couscous.ast.ClassNode; import org.zwobble.couscous.ast.ConstructorCallNode; +import org.zwobble.couscous.ast.ConstructorNode; import org.zwobble.couscous.ast.ExpressionStatementNode; +import org.zwobble.couscous.ast.FieldAccessNode; import org.zwobble.couscous.ast.LiteralNode; import org.zwobble.couscous.ast.LocalVariableDeclarationNode; import org.zwobble.couscous.ast.MethodCallNode; @@ -12,6 +14,7 @@ import org.zwobble.couscous.ast.Node; import org.zwobble.couscous.ast.ReturnNode; import org.zwobble.couscous.ast.StaticMethodCallNode; import org.zwobble.couscous.ast.TernaryConditionalNode; +import org.zwobble.couscous.ast.ThisReferenceNode; import org.zwobble.couscous.ast.VariableReferenceNode; import lombok.val; @@ -28,12 +31,17 @@ public class NodeVisitors { public void visit(VariableReferenceNode variableReference) { visitor.visit(variableReference); } + + @Override + public void visit(ThisReferenceNode reference) { + visitor.visit(reference); + } @Override public void visit(AssignmentNode assignment) { visitor.visit(assignment); assignment.getValue().accept(this); - visit(assignment.getTarget()); + assignment.getTarget().accept(this); } @Override @@ -68,6 +76,11 @@ public class NodeVisitors { argument.accept(this); } } + + @Override + public void visit(FieldAccessNode fieldAccess) { + visitor.visit(fieldAccess); + } @Override public void visit(ReturnNode returnNode) { @@ -90,6 +103,7 @@ public class NodeVisitors { @Override public void visit(ClassNode classNode) { visitor.visit(classNode); + classNode.getConstructor().accept(this); for (val method : classNode.getMethods()) { method.accept(this); } @@ -101,6 +115,13 @@ public class NodeVisitors { statement.accept(this); } } + + @Override + public void visit(ConstructorNode constructorNode) { + for (val statement : constructorNode.getBody()) { + statement.accept(this); + } + } }); } } diff --git a/src/main/java/org/zwobble/couscous/backends/python/PythonCodeGenerator.java b/src/main/java/org/zwobble/couscous/backends/python/PythonCodeGenerator.java index 1065f65..9b3367f 100644 --- a/src/main/java/org/zwobble/couscous/backends/python/PythonCodeGenerator.java +++ b/src/main/java/org/zwobble/couscous/backends/python/PythonCodeGenerator.java @@ -8,8 +8,10 @@ import java.util.stream.Stream; import org.zwobble.couscous.ast.AssignmentNode; import org.zwobble.couscous.ast.ClassNode; import org.zwobble.couscous.ast.ConstructorCallNode; +import org.zwobble.couscous.ast.ConstructorNode; import org.zwobble.couscous.ast.ExpressionNode; import org.zwobble.couscous.ast.ExpressionStatementNode; +import org.zwobble.couscous.ast.FieldAccessNode; import org.zwobble.couscous.ast.LiteralNode; import org.zwobble.couscous.ast.LocalVariableDeclarationNode; import org.zwobble.couscous.ast.MethodCallNode; @@ -18,6 +20,7 @@ import org.zwobble.couscous.ast.ReturnNode; import org.zwobble.couscous.ast.StatementNode; import org.zwobble.couscous.ast.StaticMethodCallNode; import org.zwobble.couscous.ast.TernaryConditionalNode; +import org.zwobble.couscous.ast.ThisReferenceNode; import org.zwobble.couscous.ast.TypeName; import org.zwobble.couscous.ast.VariableReferenceNode; import org.zwobble.couscous.ast.visitors.ExpressionNodeMapper; @@ -65,19 +68,27 @@ import lombok.val; public class PythonCodeGenerator { public static PythonModuleNode generateCode(ClassNode classNode) { val imports = generateImports(classNode).iterator(); + + val constructor = generateConstructor(classNode.getConstructor()); + + val pythonMethods = Iterables.transform( + classNode.getMethods(), + PythonCodeGenerator::generateFunction); - val pythonBody = classNode.getMethods() - .stream() - .map(PythonCodeGenerator::generateFunction) - .collect(Collectors.toList()); - val pythonClass = pythonClass(classNode.getSimpleName(), pythonBody); + val pythonBody = Iterables.concat( + asList(constructor), + pythonMethods); + + val pythonClass = pythonClass( + classNode.getSimpleName(), + ImmutableList.copyOf(pythonBody)); return pythonModule(ImmutableList.copyOf(Iterators.concat( imports, singletonIterator(pythonClass)))); } - + private static Stream generateImports(ClassNode classNode) { val classes = findReferencedClasses(classNode); return classes.stream() @@ -126,6 +137,27 @@ public class PythonCodeGenerator { }); } + private static PythonFunctionDefinitionNode generateConstructor( + ConstructorNode constructor) { + val explicitArgumentNames = Iterables.transform( + constructor.getArguments(), + argument -> argument.getName()); + + val argumentNames = Iterables.concat( + asList("self"), + explicitArgumentNames); + + val pythonBody = constructor.getBody() + .stream() + .map(PythonCodeGenerator::generateStatement) + .collect(Collectors.toList()); + + return pythonFunctionDefinition( + "__init__", + ImmutableList.copyOf(argumentNames), + new PythonBlock(pythonBody)); + } + private static PythonFunctionDefinitionNode generateFunction(MethodNode method) { val explicitArgumentNames = Iterables.transform( method.getArguments(), @@ -161,7 +193,7 @@ public class PythonCodeGenerator { if (expressionStatement.getExpression() instanceof AssignmentNode) { val assignment = (AssignmentNode) expressionStatement.getExpression(); return pythonAssignment( - EXPRESSION_GENERATOR.visit(assignment.getTarget()), + assignment.getTarget().accept(EXPRESSION_GENERATOR), generateExpression(assignment.getValue())); } else { throw new UnsupportedOperationException(); @@ -198,6 +230,11 @@ public class PythonCodeGenerator { return pythonVariableReference(variableReference.getReferent().getName()); } + @Override + public PythonExpressionNode visit(ThisReferenceNode reference) { + return pythonVariableReference("self"); + } + @Override public PythonExpressionNode visit(AssignmentNode assignment) { throw new UnsupportedOperationException(); @@ -249,6 +286,13 @@ public class PythonCodeGenerator { return pythonCall(classReference, arguments); } + + @Override + public PythonExpressionNode visit(FieldAccessNode fieldAccess) { + return pythonAttributeAccess( + generateExpression(fieldAccess.getLeft()), + fieldAccess.getFieldName()); + } } private static boolean isPrimitive(ExpressionNode value) { diff --git a/src/main/java/org/zwobble/couscous/backends/python/ast/PythonAssignmentNode.java b/src/main/java/org/zwobble/couscous/backends/python/ast/PythonAssignmentNode.java index 3624908..9c5b20a 100644 --- a/src/main/java/org/zwobble/couscous/backends/python/ast/PythonAssignmentNode.java +++ b/src/main/java/org/zwobble/couscous/backends/python/ast/PythonAssignmentNode.java @@ -6,7 +6,7 @@ import lombok.Value; @Value(staticConstructor="pythonAssignment") public class PythonAssignmentNode implements PythonStatementNode { - PythonVariableReferenceNode target; + PythonExpressionNode target; PythonExpressionNode value; @Override diff --git a/src/main/java/org/zwobble/couscous/interpreter/Environment.java b/src/main/java/org/zwobble/couscous/interpreter/Environment.java index 6da54ce..0bbc808 100644 --- a/src/main/java/org/zwobble/couscous/interpreter/Environment.java +++ b/src/main/java/org/zwobble/couscous/interpreter/Environment.java @@ -19,16 +19,25 @@ import lombok.val; public class Environment { private final Project project; + private final Optional thisValue; private final Map stackFrame; - public Environment(Project project, Map> stackFrame) { + public Environment( + Project project, + Optional thisValue, + Map> stackFrame) { this.project = project; + this.thisValue = thisValue; this.stackFrame = stackFrame.entrySet() .stream() .collect(toMap( entry -> entry.getKey().getId(), entry -> VariableEntry.of(entry.getKey().getType(), entry.getValue()))); } + + public Optional getThis() { + return thisValue; + } public InterpreterValue get(int variableId) { checkVariableIsInScope(variableId); @@ -51,8 +60,8 @@ public class Environment { return project.findClass(className); } - public Environment withStackFrame(Map> stackFrame) { - return new Environment(project, stackFrame); + public Environment withStackFrame(Optional thisValue, Map> stackFrame) { + return new Environment(project, thisValue, stackFrame); } private void checkVariableIsInScope(int variableId) { diff --git a/src/main/java/org/zwobble/couscous/interpreter/Evaluator.java b/src/main/java/org/zwobble/couscous/interpreter/Evaluator.java index ee67da0..18d7109 100644 --- a/src/main/java/org/zwobble/couscous/interpreter/Evaluator.java +++ b/src/main/java/org/zwobble/couscous/interpreter/Evaluator.java @@ -6,15 +6,20 @@ import java.util.stream.Collectors; import org.zwobble.couscous.ast.AssignmentNode; import org.zwobble.couscous.ast.ConstructorCallNode; import org.zwobble.couscous.ast.ExpressionNode; +import org.zwobble.couscous.ast.FieldAccessNode; import org.zwobble.couscous.ast.LiteralNode; import org.zwobble.couscous.ast.MethodCallNode; import org.zwobble.couscous.ast.StaticMethodCallNode; import org.zwobble.couscous.ast.TernaryConditionalNode; +import org.zwobble.couscous.ast.ThisReferenceNode; import org.zwobble.couscous.ast.VariableReferenceNode; +import org.zwobble.couscous.ast.visitors.AssignableExpressionNodeVisitor; import org.zwobble.couscous.ast.visitors.ExpressionNodeMapper; import org.zwobble.couscous.interpreter.values.BooleanInterpreterValue; import org.zwobble.couscous.interpreter.values.InterpreterValue; import org.zwobble.couscous.interpreter.values.InterpreterValues; +import org.zwobble.couscous.interpreter.values.ObjectInterpreterValue; +import org.zwobble.couscous.util.Casts; import lombok.val; @@ -43,10 +48,29 @@ public class Evaluator implements ExpressionNodeMapper { return environment.get(variableReference.getReferentId()); } + @Override + public InterpreterValue visit(ThisReferenceNode reference) { + return environment.getThis().get(); + } + @Override public InterpreterValue visit(AssignmentNode assignment) { val value = eval(assignment.getValue()); - environment.put(assignment.getTarget().getReferentId(), value); + assignment.getTarget().accept(new AssignableExpressionNodeVisitor() { + @Override + public void visit(FieldAccessNode fieldAccess) { + val maybeLeft = Casts.tryCast( + ObjectInterpreterValue.class, + eval(fieldAccess.getLeft())); + val left = maybeLeft.orElseThrow(() -> new UnsupportedOperationException()); + left.setField(fieldAccess.getFieldName(), value); + } + + @Override + public void visit(VariableReferenceNode reference) { + environment.put(reference.getReferentId(), value); + } + }); return value; } @@ -84,6 +108,14 @@ public class Evaluator implements ExpressionNodeMapper { return clazz.callConstructor(environment, arguments); } + @Override + public InterpreterValue visit(FieldAccessNode fieldAccess) { + // TODO: handle unbound fields + // TODO: handle non-existent fields + val left = eval(fieldAccess.getLeft()); + return left.getField(fieldAccess.getFieldName()); + } + private List evalArguments(List arguments) { return arguments .stream() diff --git a/src/main/java/org/zwobble/couscous/interpreter/Executor.java b/src/main/java/org/zwobble/couscous/interpreter/Executor.java index e9304cb..5c26244 100644 --- a/src/main/java/org/zwobble/couscous/interpreter/Executor.java +++ b/src/main/java/org/zwobble/couscous/interpreter/Executor.java @@ -4,9 +4,9 @@ import java.util.List; import java.util.Optional; import java.util.stream.Stream; +import org.zwobble.couscous.ast.CallableNode; import org.zwobble.couscous.ast.ExpressionStatementNode; import org.zwobble.couscous.ast.LocalVariableDeclarationNode; -import org.zwobble.couscous.ast.MethodNode; import org.zwobble.couscous.ast.ReturnNode; import org.zwobble.couscous.ast.StatementNode; import org.zwobble.couscous.ast.VariableNode; @@ -19,8 +19,13 @@ import static org.zwobble.couscous.interpreter.Evaluator.eval; import lombok.val; public class Executor implements StatementNodeMapper> { - public static InterpreterValue callMethod(Environment environment, MethodNode method, PositionalArguments arguments) { - val innerEnvironment = buildEnvironment(environment, method, arguments); + public static InterpreterValue callMethod( + Environment environment, + CallableNode method, + Optional thisValue, + PositionalArguments arguments) { + + val innerEnvironment = buildEnvironment(environment, method, thisValue, arguments); for (val statement : method.getBody()) { val result = exec(innerEnvironment, statement); @@ -32,9 +37,10 @@ public class Executor implements StatementNodeMapper> } private static Environment buildEnvironment( - final Environment environment, - final MethodNode method, - PositionalArguments arguments) { + final Environment environment, + final CallableNode method, + Optional thisValue, + PositionalArguments arguments) { val stackFrame = new StackFrameBuilder(); for (int index = 0; index < method.getArguments().size(); index++) { @@ -44,7 +50,7 @@ public class Executor implements StatementNodeMapper> findDeclarations(method.getBody()).forEach(declaration -> stackFrame.declare(declaration)); - return environment.withStackFrame(stackFrame.build()); + return environment.withStackFrame(thisValue, stackFrame.build()); } private static Stream findDeclarations(List body) { diff --git a/src/main/java/org/zwobble/couscous/interpreter/Interpreter.java b/src/main/java/org/zwobble/couscous/interpreter/Interpreter.java index 38ea2ac..88e9d0e 100644 --- a/src/main/java/org/zwobble/couscous/interpreter/Interpreter.java +++ b/src/main/java/org/zwobble/couscous/interpreter/Interpreter.java @@ -1,6 +1,7 @@ package org.zwobble.couscous.interpreter; import java.util.List; +import java.util.Optional; import java.util.stream.Collectors; import org.zwobble.couscous.Project; @@ -28,7 +29,7 @@ public class Interpreter { .stream() .map(argument -> literal(argument.toPrimitiveValue().get())) .collect(Collectors.toList()); - return eval(new Environment(project, ImmutableMap.of()), + return eval(new Environment(project, Optional.empty(), ImmutableMap.of()), staticMethodCall(className, methodName, argumentExpressions)); } } diff --git a/src/main/java/org/zwobble/couscous/interpreter/NoSuchField.java b/src/main/java/org/zwobble/couscous/interpreter/NoSuchField.java new file mode 100644 index 0000000..31feaab --- /dev/null +++ b/src/main/java/org/zwobble/couscous/interpreter/NoSuchField.java @@ -0,0 +1,12 @@ +package org.zwobble.couscous.interpreter; + +import lombok.EqualsAndHashCode; +import lombok.Value; + +@Value +@EqualsAndHashCode(callSuper=false) +public class NoSuchField extends InterpreterException { + private static final long serialVersionUID = 1L; + + String fieldName; +} diff --git a/src/main/java/org/zwobble/couscous/interpreter/values/BooleanInterpreterValue.java b/src/main/java/org/zwobble/couscous/interpreter/values/BooleanInterpreterValue.java index b98eb20..06e8bee 100644 --- a/src/main/java/org/zwobble/couscous/interpreter/values/BooleanInterpreterValue.java +++ b/src/main/java/org/zwobble/couscous/interpreter/values/BooleanInterpreterValue.java @@ -2,6 +2,7 @@ package org.zwobble.couscous.interpreter.values; import java.util.Optional; +import org.zwobble.couscous.interpreter.NoSuchField; import org.zwobble.couscous.values.PrimitiveValue; import org.zwobble.couscous.values.PrimitiveValues; @@ -30,4 +31,9 @@ public class BooleanInterpreterValue implements InterpreterValue { public Optional toPrimitiveValue() { return Optional.of(PrimitiveValues.value(value)); } + + @Override + public InterpreterValue getField(String fieldName) { + throw new NoSuchField(fieldName); + } } diff --git a/src/main/java/org/zwobble/couscous/interpreter/values/ConcreteType.java b/src/main/java/org/zwobble/couscous/interpreter/values/ConcreteType.java index 70ba96a..bd63d7f 100644 --- a/src/main/java/org/zwobble/couscous/interpreter/values/ConcreteType.java +++ b/src/main/java/org/zwobble/couscous/interpreter/values/ConcreteType.java @@ -2,10 +2,12 @@ package org.zwobble.couscous.interpreter.values; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.function.BiFunction; import java.util.stream.Collectors; import org.zwobble.couscous.ast.ClassNode; +import org.zwobble.couscous.ast.FormalArgumentNode; import org.zwobble.couscous.ast.TypeName; import org.zwobble.couscous.interpreter.PositionalArguments; import org.zwobble.couscous.interpreter.Environment; @@ -15,6 +17,7 @@ import org.zwobble.couscous.interpreter.UnexpectedValueType; import org.zwobble.couscous.interpreter.WrongNumberOfArguments; import org.zwobble.couscous.util.Casts; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import static java.util.stream.Collectors.toMap; @@ -57,22 +60,38 @@ public class ConcreteType { } public ConcreteType build() { - return new ConcreteType(name, methods.build(), staticMethods.build()); + return new ConcreteType( + name, + new MethodValue( + ImmutableList.of(), + (environment, arguments) -> InterpreterValues.UNIT), + methods.build(), + staticMethods.build()); } } public static ConcreteType fromNode(ClassNode classNode) { + val constructor = new MethodValue( + getArgumentTypes(classNode.getConstructor().getArguments()), + (environment, arguments) -> Executor.callMethod( + environment, + classNode.getConstructor(), + Optional.of(arguments.getReceiver()), + arguments.getPositionalArguments())); + val methods = classNode.getMethods().stream() .filter(method -> !method.isStatic()) .collect(toMap( method -> method.getName(), method -> { - List argumentTypes = method.getArguments() - .stream() - .map(arg -> arg.getType()) - .collect(Collectors.toList()); + val argumentTypes = getArgumentTypes(method.getArguments()); + return new MethodValue(argumentTypes, (environment, arguments) -> { - return Executor.callMethod(environment, method, arguments.getPositionalArguments()); + return Executor.callMethod( + environment, + method, + Optional.of(arguments.getReceiver()), + arguments.getPositionalArguments()); }); })); @@ -82,29 +101,42 @@ public class ConcreteType { .collect(toMap( method -> method.getName(), method -> { - List argumentTypes = method.getArguments() - .stream() - .map(arg -> arg.getType()) - .collect(Collectors.toList()); + val argumentTypes = getArgumentTypes(method.getArguments()); + return new StaticMethodValue(argumentTypes, (environment, arguments) -> { - return Executor.callMethod(environment, method, arguments); + return Executor.callMethod( + environment, + method, + Optional.empty(), + arguments); }); })); return new ConcreteType( classNode.getName(), + constructor, methods, staticMethods); } + + private static List getArgumentTypes(List arguments) { + return arguments + .stream() + .map(arg -> arg.getType()) + .collect(Collectors.toList()); + } - private TypeName name; - private Map methods; - private Map staticMethods; + private final TypeName name; + private final MethodValue constructor; + private final Map methods; + private final Map staticMethods; public ConcreteType( TypeName name, + MethodValue constructor, Map methods, Map staticMethods) { this.name = name; + this.constructor = constructor; this.methods = methods; this.staticMethods = staticMethods; } @@ -136,7 +168,11 @@ public class ConcreteType { public InterpreterValue callConstructor( Environment environment, List arguments) { - return new ObjectInterpreterValue(this); + val object = new ObjectInterpreterValue(this); + constructor.apply( + environment, + MethodCallArguments.of(object, new PositionalArguments(arguments))); + return object; } private static T findMethod(Map methods, String methodName, List arguments) { diff --git a/src/main/java/org/zwobble/couscous/interpreter/values/IntegerInterpreterValue.java b/src/main/java/org/zwobble/couscous/interpreter/values/IntegerInterpreterValue.java index 92ba306..1effe6a 100644 --- a/src/main/java/org/zwobble/couscous/interpreter/values/IntegerInterpreterValue.java +++ b/src/main/java/org/zwobble/couscous/interpreter/values/IntegerInterpreterValue.java @@ -2,6 +2,7 @@ package org.zwobble.couscous.interpreter.values; import java.util.Optional; +import org.zwobble.couscous.interpreter.NoSuchField; import org.zwobble.couscous.values.IntegerValue; import org.zwobble.couscous.values.PrimitiveValue; import org.zwobble.couscous.values.PrimitiveValues; @@ -34,4 +35,9 @@ public class IntegerInterpreterValue implements InterpreterValue { public Optional toPrimitiveValue() { return Optional.of(PrimitiveValues.value(value)); } + + @Override + public InterpreterValue getField(String fieldName) { + throw new NoSuchField(fieldName); + } } diff --git a/src/main/java/org/zwobble/couscous/interpreter/values/InterpreterValue.java b/src/main/java/org/zwobble/couscous/interpreter/values/InterpreterValue.java index 6c3c2ec..78d27d2 100644 --- a/src/main/java/org/zwobble/couscous/interpreter/values/InterpreterValue.java +++ b/src/main/java/org/zwobble/couscous/interpreter/values/InterpreterValue.java @@ -7,4 +7,5 @@ import org.zwobble.couscous.values.PrimitiveValue; public interface InterpreterValue { ConcreteType getType(); Optional toPrimitiveValue(); + InterpreterValue getField(String fieldName); } diff --git a/src/main/java/org/zwobble/couscous/interpreter/values/ObjectInterpreterValue.java b/src/main/java/org/zwobble/couscous/interpreter/values/ObjectInterpreterValue.java index 4383353..acb6ef7 100644 --- a/src/main/java/org/zwobble/couscous/interpreter/values/ObjectInterpreterValue.java +++ b/src/main/java/org/zwobble/couscous/interpreter/values/ObjectInterpreterValue.java @@ -1,14 +1,18 @@ package org.zwobble.couscous.interpreter.values; +import java.util.HashMap; +import java.util.Map; import java.util.Optional; import org.zwobble.couscous.values.PrimitiveValue; public class ObjectInterpreterValue implements InterpreterValue { private final ConcreteType type; + private final Map fields; public ObjectInterpreterValue(ConcreteType type) { this.type = type; + this.fields = new HashMap<>(); } @Override @@ -20,4 +24,17 @@ public class ObjectInterpreterValue implements InterpreterValue { public Optional toPrimitiveValue() { return Optional.empty(); } + + @Override + public InterpreterValue getField(String fieldName) { + if (fields.containsKey(fieldName)) { + return fields.get(fieldName); + } else { + throw new UnsupportedOperationException(); + } + } + + public void setField(String fieldName, InterpreterValue value) { + fields.put(fieldName, value); + } } diff --git a/src/main/java/org/zwobble/couscous/interpreter/values/StringInterpreterValue.java b/src/main/java/org/zwobble/couscous/interpreter/values/StringInterpreterValue.java index aa3432a..a4dde19 100644 --- a/src/main/java/org/zwobble/couscous/interpreter/values/StringInterpreterValue.java +++ b/src/main/java/org/zwobble/couscous/interpreter/values/StringInterpreterValue.java @@ -2,6 +2,7 @@ package org.zwobble.couscous.interpreter.values; import java.util.Optional; +import org.zwobble.couscous.interpreter.NoSuchField; import org.zwobble.couscous.values.IntegerValue; import org.zwobble.couscous.values.PrimitiveValue; import org.zwobble.couscous.values.PrimitiveValues; @@ -39,4 +40,9 @@ public class StringInterpreterValue implements InterpreterValue { public Optional toPrimitiveValue() { return Optional.of(PrimitiveValues.value(value)); } + + @Override + public InterpreterValue getField(String fieldName) { + throw new NoSuchField(fieldName); + } } diff --git a/src/main/java/org/zwobble/couscous/interpreter/values/UnitInterpreterValue.java b/src/main/java/org/zwobble/couscous/interpreter/values/UnitInterpreterValue.java index 253932f..e221fee 100644 --- a/src/main/java/org/zwobble/couscous/interpreter/values/UnitInterpreterValue.java +++ b/src/main/java/org/zwobble/couscous/interpreter/values/UnitInterpreterValue.java @@ -2,6 +2,7 @@ package org.zwobble.couscous.interpreter.values; import java.util.Optional; +import org.zwobble.couscous.interpreter.NoSuchField; import org.zwobble.couscous.values.PrimitiveValue; import org.zwobble.couscous.values.PrimitiveValues; @@ -26,4 +27,9 @@ public class UnitInterpreterValue implements InterpreterValue { public Optional toPrimitiveValue() { return Optional.of(PrimitiveValues.UNIT); } + + @Override + public InterpreterValue getField(String fieldName) { + throw new NoSuchField(fieldName); + } } -- cgit v1.2.3