Initial commit

This commit is contained in:
github-classroom[bot]
2021-04-04 18:11:58 +00:00
commit 9f1681bb23
342 changed files with 35807 additions and 0 deletions
+54
View File
@@ -0,0 +1,54 @@
package chocopy.common;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.util.stream.Collectors;
/** Utility functions for general use. */
public class Utils {
/**
* Return resource file FILENAME's contents as a string. FILENAME can refer to a file within the
* class hierarchy, so that a text resource in file resource.txt in the chocopy.common.codegen
* package, for example, could be referred to with FILENAME chocopy/common/codegen/resource.txt.
*
* <p>Credit: Lucio Paiva.
*/
public static String getResourceFileAsString(String fileName) {
InputStream is = Utils.class.getClassLoader().getResourceAsStream(fileName);
if (is != null) {
BufferedReader reader =
new BufferedReader(new InputStreamReader(is, Charset.forName("UTF-8")));
return reader.lines().collect(Collectors.joining(System.lineSeparator()));
}
return null;
}
/**
* Return an exception signalling a fatal error having a message formed from MSGFORMAT and ARGS,
* as for String.format.
*/
public static Error fatal(String msgFormat, Object... args) {
return new Error(String.format(msgFormat, args));
}
/**
* Return the string S padded with FILL to TOLEN characters. Padding is on the left if
* PADONLEFT, and otherwise on the right. If S is already at least TOLEN characters, returns S.
*/
public static String pad(String s, Character fill, int toLen, boolean padOnLeft) {
StringBuilder result = new StringBuilder(toLen);
if (!padOnLeft) {
result.append(s);
}
for (int n = s.length(); n < toLen; n += 1) {
result.append(fill);
}
if (padOnLeft) {
result.append(s);
}
return result.toString();
}
}
@@ -0,0 +1,174 @@
package chocopy.common.analysis;
import chocopy.common.astnodes.*;
/**
* An empty implementation of the {@link NodeAnalyzer} that simply returns does nothing and returns
* null for every AST node type.
*
* <p>T is the type of analysis result.
*/
public class AbstractNodeAnalyzer<T> implements NodeAnalyzer<T> {
@Override
public T analyze(AssignStmt node) {
return defaultAction(node);
}
@Override
public T analyze(BinaryExpr node) {
return defaultAction(node);
}
@Override
public T analyze(BooleanLiteral node) {
return defaultAction(node);
}
@Override
public T analyze(CallExpr node) {
return defaultAction(node);
}
@Override
public T analyze(ClassDef node) {
return defaultAction(node);
}
@Override
public T analyze(ClassType node) {
return defaultAction(node);
}
@Override
public T analyze(CompilerError node) {
return defaultAction(node);
}
@Override
public T analyze(Errors node) {
return defaultAction(node);
}
@Override
public T analyze(ExprStmt node) {
return defaultAction(node);
}
@Override
public T analyze(ForStmt node) {
return defaultAction(node);
}
@Override
public T analyze(FuncDef node) {
return defaultAction(node);
}
@Override
public T analyze(GlobalDecl node) {
return defaultAction(node);
}
@Override
public T analyze(Identifier node) {
return defaultAction(node);
}
@Override
public T analyze(IfExpr node) {
return defaultAction(node);
}
@Override
public T analyze(IfStmt node) {
return defaultAction(node);
}
@Override
public T analyze(IndexExpr node) {
return defaultAction(node);
}
@Override
public T analyze(IntegerLiteral node) {
return defaultAction(node);
}
@Override
public T analyze(ListExpr node) {
return defaultAction(node);
}
@Override
public T analyze(ListType node) {
return defaultAction(node);
}
@Override
public T analyze(MemberExpr node) {
return defaultAction(node);
}
@Override
public T analyze(MethodCallExpr node) {
return defaultAction(node);
}
@Override
public T analyze(NoneLiteral node) {
return defaultAction(node);
}
@Override
public T analyze(NonLocalDecl node) {
return defaultAction(node);
}
@Override
public T analyze(Program node) {
return defaultAction(node);
}
@Override
public T analyze(ReturnStmt node) {
return defaultAction(node);
}
@Override
public T analyze(StringLiteral node) {
return defaultAction(node);
}
@Override
public T analyze(TypedVar node) {
return defaultAction(node);
}
@Override
public T analyze(UnaryExpr node) {
return defaultAction(node);
}
@Override
public T analyze(VarDef node) {
return defaultAction(node);
}
@Override
public T analyze(WhileStmt node) {
return defaultAction(node);
}
@Override
public void setDefault(T value) {
defaultValue = value;
}
@Override
public T defaultAction(Node node) {
return defaultValue;
}
/** Default value for non-overridden methods. */
private T defaultValue = null;
}
@@ -0,0 +1,93 @@
package chocopy.common.analysis;
import chocopy.common.astnodes.*;
/**
* This interface can be used to separate logic for various concrete classes in the AST class
* hierarchy.
*
* <p>The idea is that a phase of the analysis is encapsulated in a class that implements this
* interface, and contains an overriding of the analyze method for each concrete Node class that
* needs something other than default processing. Each concrete node class, C, implements a generic
* dispatch method that takes a NodeAnalyzer<T> argument and calls the overloading of analyze that
* takes an argument of type C. The effect is that anode.dispatch(anAnalyzer) executes the method
* anAnalyzer.analyze that is appropriate to aNode's dynamic type. As a result each NodeAnalyzer
* subtype encapsulates all implementations of a particular action on Nodes. Thus, it inverts the
* usual OO pattern in which the implementations of analysis A for each different class are
* scattered among the class bodies themselves as overridings of a method A on the Node class.
*
* <p>The class AbstractNodeAnalyzer provides empty default implementations for these methods.
*
* <p>The type T is the type of result returned by the encapsulated analysis.
*/
public interface NodeAnalyzer<T> {
T analyze(AssignStmt node);
T analyze(BinaryExpr node);
T analyze(BooleanLiteral node);
T analyze(CallExpr node);
T analyze(ClassDef node);
T analyze(ClassType node);
T analyze(CompilerError node);
T analyze(Errors node);
T analyze(ExprStmt node);
T analyze(ForStmt node);
T analyze(FuncDef node);
T analyze(GlobalDecl node);
T analyze(Identifier node);
T analyze(IfExpr node);
T analyze(IfStmt node);
T analyze(IndexExpr node);
T analyze(IntegerLiteral node);
T analyze(ListExpr node);
T analyze(ListType node);
T analyze(MemberExpr node);
T analyze(MethodCallExpr node);
T analyze(NoneLiteral node);
T analyze(NonLocalDecl node);
T analyze(Program node);
T analyze(ReturnStmt node);
T analyze(StringLiteral node);
T analyze(TypedVar node);
T analyze(UnaryExpr node);
T analyze(VarDef node);
T analyze(WhileStmt node);
/**
* Set the default value returned by calls to analyze that are not overridden to VALUE. By
* default, this is null.
*/
void setDefault(T value);
/** Default value for non-overridden methods. */
T defaultAction(Node node);
}
@@ -0,0 +1,62 @@
package chocopy.common.analysis;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/**
* A block-structured symbol table a mapping identifiers to information about them of type T in a
* given declarative region.
*/
public class SymbolTable<T> {
/** Contents of the current (innermost) region. */
private final Map<String, T> tab = new HashMap<>();
/** Enclosing block. */
private final SymbolTable<T> parent;
/** A table representing a region nested in that represented by PARENT0. */
public SymbolTable(SymbolTable<T> parent0) {
parent = parent0;
}
/** A top-level symbol table. */
public SymbolTable() {
this.parent = null;
}
/** Returns the mapping of NAME in the innermost nested region containing this one. */
public T get(String name) {
if (tab.containsKey(name)) {
return tab.get(name);
} else if (parent != null) {
return parent.get(name);
} else {
return null;
}
}
/**
* Adds a new mapping of NAME -> VALUE to the current region, possibly shadowing mappings in the
* enclosing parent. Returns modified table.
*/
public SymbolTable<T> put(String name, T value) {
tab.put(name, value);
return this;
}
/** Returns whether NAME has a mapping in this region (ignoring enclosing regions. */
public boolean declares(String name) {
return tab.containsKey(name);
}
/** Returns all the names declared this region (ignoring enclosing regions). */
public Set<String> getDeclaredSymbols() {
return tab.keySet();
}
/** Returns the parent, or null if this is the top level. */
public SymbolTable<T> getParent() {
return this.parent;
}
}
@@ -0,0 +1,53 @@
package chocopy.common.analysis.types;
import chocopy.common.astnodes.ClassType;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.Objects;
/** Represents the semantic value of a simple class reference. */
public class ClassValueType extends ValueType {
/** The name of the class. */
private final String className;
/** A class type for the class named CLASSNAME. */
@JsonCreator
public ClassValueType(@JsonProperty String className) {
this.className = className;
}
/** A class type for the class referenced by CLASSTYPEANNOTATION. */
public ClassValueType(ClassType classTypeAnnotation) {
this.className = classTypeAnnotation.className;
}
@Override
@JsonProperty
public String className() {
return className;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
ClassValueType classType = (ClassValueType) o;
return Objects.equals(className, classType.className);
}
@Override
public int hashCode() {
return Objects.hash(className);
}
@Override
public String toString() {
return className;
}
}
@@ -0,0 +1,45 @@
package chocopy.common.analysis.types;
import com.fasterxml.jackson.annotation.JsonCreator;
import java.util.ArrayList;
import java.util.List;
/** Semantic information for a function or method. */
public class FuncType extends Type {
/** Types of parameters. */
public final List<ValueType> parameters;
/** Function's return type. */
public final ValueType returnType;
/** Create a FuncType returning RETURNTYPE0, initially parameterless. */
public FuncType(ValueType returnType0) {
this(new ArrayList<>(), returnType0);
}
/**
* Create a FuncType for NAME0 with formal parameter types PARAMETERS0, returning type
* RETURNTYPE0.
*/
@JsonCreator
public FuncType(List<ValueType> parameters0, ValueType returnType0) {
this.parameters = parameters0;
this.returnType = returnType0;
}
@Override
public boolean isFuncType() {
return true;
}
/** Return the type of the K-th parameter. */
public ValueType getParamType(int k) {
return parameters.get(k);
}
@Override
public String toString() {
return "<function>";
}
}
@@ -0,0 +1,57 @@
package chocopy.common.analysis.types;
import chocopy.common.astnodes.ListType;
import com.fasterxml.jackson.annotation.JsonCreator;
import java.util.Objects;
/** Represents a semantic value of a list type denotation. */
public class ListValueType extends ValueType {
/** This ListValueType represents [ELEMENTTYPE]. */
public final ValueType elementType;
/** Represents [ELEMENTTYPE]. */
@JsonCreator
public ListValueType(Type elementType) {
this.elementType = (ValueType) elementType;
}
/** Represents [<type>], where <type> is that denoted in TYPEANNOTATION. */
public ListValueType(ListType typeAnnotation) {
elementType = ValueType.annotationToValueType(typeAnnotation.elementType);
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
ListValueType listType = (ListValueType) o;
return Objects.equals(elementType, listType.elementType);
}
@Override
public int hashCode() {
return Objects.hash(elementType);
}
@Override
public String toString() {
return "[" + elementType.toString() + "]";
}
/** Returns true iff I represent [T]. */
@Override
public boolean isListType() {
return true;
}
@Override
public ValueType elementType() {
return elementType;
}
}
@@ -0,0 +1,68 @@
package chocopy.common.analysis.types;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
/**
* Representation for the static type of symbols and expressions during type-checking.
*
* <p>Symbols such as variables and attributes will typically map to a {@link ValueType}.
*
* <p>Symbols such as classes will typically map to a more complex Type.
*/
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "kind")
@JsonSubTypes({
@JsonSubTypes.Type(FuncType.class),
@JsonSubTypes.Type(ClassValueType.class),
@JsonSubTypes.Type(ListValueType.class)
})
public abstract class Type {
/** The type object. */
public static final ClassValueType OBJECT_TYPE = new ClassValueType("object");
/** The type int. */
public static final ClassValueType INT_TYPE = new ClassValueType("int");
/** The type str. */
public static final ClassValueType STR_TYPE = new ClassValueType("str");
/** The type bool. */
public static final ClassValueType BOOL_TYPE = new ClassValueType("bool");
/** The type of None. */
public static final ClassValueType NONE_TYPE = new ClassValueType("<None>");
/** The type of []. */
public static final ClassValueType EMPTY_TYPE = new ClassValueType("<Empty>");
/** Returns the name of the class, if this is a class type, Otherwise null. */
public String className() {
return null;
}
/** Return true iff this is a type that does not include the value None. */
@JsonIgnore
public boolean isSpecialType() {
return equals(INT_TYPE) || equals(BOOL_TYPE) || equals(STR_TYPE);
}
@JsonIgnore
public boolean isListType() {
return false;
}
@JsonIgnore
public boolean isFuncType() {
return false;
}
/** Return true iff this type represents a kind of assignable value. */
@JsonIgnore
public boolean isValueType() {
return false;
}
/** For list types, return the type of the elements; otherwise null. */
@JsonIgnore
public ValueType elementType() {
return null;
}
}
@@ -0,0 +1,29 @@
package chocopy.common.analysis.types;
import chocopy.common.astnodes.ClassType;
import chocopy.common.astnodes.ListType;
import chocopy.common.astnodes.TypeAnnotation;
/**
* A ValueType references types that are assigned to variables and expressions.
*
* <p>In particular, ValueType can be a {@link ClassValueType} (e.g. "int") or a {@link
* ListValueType} (e.g. "[int]").
*/
public abstract class ValueType extends Type {
/** Returns the type corresponding to ANNOTATION. */
public static ValueType annotationToValueType(TypeAnnotation annotation) {
if (annotation instanceof ClassType) {
return new ClassValueType((ClassType) annotation);
} else {
assert annotation instanceof ListType;
return new ListValueType((ListType) annotation);
}
}
@Override
public boolean isValueType() {
return true;
}
}
@@ -0,0 +1,25 @@
package chocopy.common.astnodes;
import chocopy.common.analysis.NodeAnalyzer;
import java_cup.runtime.ComplexSymbolFactory.Location;
import java.util.List;
/** Single and multiple assignments. */
public class AssignStmt extends Stmt {
/** List of left-hand sides. */
public final List<Expr> targets;
/** Right-hand-side value to be assigned. */
public final Expr value;
/** AST for TARGETS[0] = TARGETS[1] = ... = VALUE spanning source locations [LEFT..RIGHT]. */
public AssignStmt(Location left, Location right, List<Expr> targets, Expr value) {
super(left, right);
this.targets = targets;
this.value = value;
}
public <T> T dispatch(NodeAnalyzer<T> analyzer) {
return analyzer.analyze(this);
}
}
@@ -0,0 +1,31 @@
package chocopy.common.astnodes;
import chocopy.common.analysis.NodeAnalyzer;
import java_cup.runtime.ComplexSymbolFactory.Location;
/** <operand> <operator> <operand>. */
public class BinaryExpr extends Expr {
/** Left operand. */
public final Expr left;
/** Operator name. */
public final String operator;
/** Right operand. */
public final Expr right;
/**
* An AST for expressions of the form LEFTEXPR OP RIGHTEXPR from text in range
* [LEFTLOC..RIGHTLOC].
*/
public BinaryExpr(
Location leftLoc, Location rightLoc, Expr leftExpr, String op, Expr rightExpr) {
super(leftLoc, rightLoc);
left = leftExpr;
operator = op;
right = rightExpr;
}
public <T> T dispatch(NodeAnalyzer<T> analyzer) {
return analyzer.analyze(this);
}
}
@@ -0,0 +1,21 @@
package chocopy.common.astnodes;
import chocopy.common.analysis.NodeAnalyzer;
import java_cup.runtime.ComplexSymbolFactory.Location;
/** Literals True or False. */
public final class BooleanLiteral extends Literal {
/** True iff I represent True. */
public final boolean value;
/** An AST for the token True or False at [LEFT..RIGHT], depending on VALUE. */
public BooleanLiteral(Location left, Location right, boolean value) {
super(left, right);
this.value = value;
}
public <T> T dispatch(NodeAnalyzer<T> analyzer) {
return analyzer.analyze(this);
}
}
@@ -0,0 +1,26 @@
package chocopy.common.astnodes;
import chocopy.common.analysis.NodeAnalyzer;
import java_cup.runtime.ComplexSymbolFactory.Location;
import java.util.List;
/** A function call. */
public class CallExpr extends Expr {
/** The called function. */
public final Identifier function;
/** The actual parameter expressions. */
public final List<Expr> args;
/** AST for FUNCTION(ARGS) at [LEFT..RIGHT]. */
public CallExpr(Location left, Location right, Identifier function, List<Expr> args) {
super(left, right);
this.function = function;
this.args = args;
}
public <T> T dispatch(NodeAnalyzer<T> analyzer) {
return analyzer.analyze(this);
}
}
@@ -0,0 +1,39 @@
package chocopy.common.astnodes;
import chocopy.common.analysis.NodeAnalyzer;
import java_cup.runtime.ComplexSymbolFactory.Location;
import java.util.List;
/** A class definition. */
public class ClassDef extends Declaration {
/** Name of the declared class. */
public final Identifier name;
/** Name of the parent class. */
public final Identifier superClass;
/** Body of the class. */
public final List<Declaration> declarations;
/** An AST for class NAME(SUPERCLASS): DECLARATIONS. spanning source locations [LEFT..RIGHT]. */
public ClassDef(
Location left,
Location right,
Identifier name,
Identifier superClass,
List<Declaration> declarations) {
super(left, right);
this.name = name;
this.superClass = superClass;
this.declarations = declarations;
}
public <T> T dispatch(NodeAnalyzer<T> analyzer) {
return analyzer.analyze(this);
}
@Override
public Identifier getIdentifier() {
return this.name;
}
}
@@ -0,0 +1,21 @@
package chocopy.common.astnodes;
import chocopy.common.analysis.NodeAnalyzer;
import java_cup.runtime.ComplexSymbolFactory.Location;
/** A simple class type name. */
public final class ClassType extends TypeAnnotation {
/** The denotation of the class in source. */
public final String className;
/** An AST denoting a type named CLASSNAME0 at [LEFT..RIGHT]. */
public ClassType(Location left, Location right, String className0) {
super(left, right);
className = className0;
}
public <T> T dispatch(NodeAnalyzer<T> analyzer) {
return analyzer.analyze(this);
}
}
@@ -0,0 +1,56 @@
package chocopy.common.astnodes;
import chocopy.common.analysis.NodeAnalyzer;
import com.fasterxml.jackson.annotation.JsonInclude;
import java_cup.runtime.ComplexSymbolFactory.Location;
import java.util.Arrays;
import java.util.Objects;
/** Represents a single error. Does not correspond to any Python source construct. */
public class CompilerError extends Node {
/**
* Represents an error with message MESSAGE. Iff SYNTAX, it is a syntactic error. The error
* applies to source text at [LEFT..RIGHT].
*/
public CompilerError(Location left, Location right, String message, boolean syntax) {
super(left, right);
this.message = message;
this.syntax = syntax;
}
@JsonInclude(JsonInclude.Include.NON_DEFAULT)
public boolean isSyntax() {
return syntax;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
CompilerError that = (CompilerError) o;
return Objects.equals(message, that.message)
&& Arrays.equals(getLocation(), that.getLocation());
}
@Override
public int hashCode() {
int result = Objects.hash(message);
result = 31 * result + Arrays.hashCode(getLocation());
return result;
}
public <T> T dispatch(NodeAnalyzer<T> analyzer) {
return analyzer.analyze(this);
}
/** The error message. */
public final String message;
/** True if this is a syntax error. */
private final boolean syntax;
}
@@ -0,0 +1,17 @@
package chocopy.common.astnodes;
import com.fasterxml.jackson.annotation.JsonIgnore;
import java_cup.runtime.ComplexSymbolFactory.Location;
/** Base of all AST nodes representing definitions or declarations. */
public abstract class Declaration extends Node {
/** A definition or declaration spanning source locations [LEFT..RIGHT]. */
public Declaration(Location left, Location right) {
super(left, right);
}
/** Return the identifier defined by this Declaration. */
@JsonIgnore
public abstract Identifier getIdentifier();
}
@@ -0,0 +1,71 @@
package chocopy.common.astnodes;
import chocopy.common.analysis.NodeAnalyzer;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore;
import java_cup.runtime.ComplexSymbolFactory.Location;
import java.util.List;
/** Collects the error messages in a Program. There is exactly one per Program node. */
public class Errors extends Node {
/** The accumulated error messages in the order added. */
public final List<CompilerError> errors;
/** True iff multiple semantic errors allowed on a node. */
@JsonIgnore private boolean allowMultipleErrors;
/**
* An Errors whose list of CompilerErrors is ERRORS. The list should be modified using this.add.
*/
@JsonCreator
public Errors(List<CompilerError> errors) {
super(null, null);
this.errors = errors;
allowMultipleErrors = true;
}
/** Return true iff there are any errors. */
public boolean hasErrors() {
return !this.errors.isEmpty();
}
/** Prevent multiple semantic errors on the same node. */
public void suppressMultipleErrors() {
allowMultipleErrors = false;
}
/**
* Add a new semantic error message attributed to NODE, with message String.format(MESSAGEFORM,
* ARGS).
*/
public void semError(Node node, String messageForm, Object... args) {
if (allowMultipleErrors || !node.hasError()) {
String msg = String.format(messageForm, args);
CompilerError err = new CompilerError(null, null, msg, false);
err.setLocation(node.getLocation());
add(err);
if (!node.hasError()) {
node.setErrorMsg(msg);
}
}
}
/**
* Add a new syntax error message attributed to the source text between LEFT and RIGHT, and with
* message String.format(MESSAGEFORM, ARGS).
*/
public void syntaxError(Location left, Location right, String messageForm, Object... args) {
add(new CompilerError(left, right, String.format(messageForm, args), true));
}
/** Add ERR to the list of errors. */
public void add(CompilerError err) {
errors.add(err);
}
public <T> T dispatch(NodeAnalyzer<T> analyzer) {
return analyzer.analyze(this);
}
}
@@ -0,0 +1,43 @@
package chocopy.common.astnodes;
import chocopy.common.analysis.types.Type;
import com.fasterxml.jackson.annotation.JsonInclude;
import java_cup.runtime.ComplexSymbolFactory.Location;
/**
* Base of all AST nodes representing expressions.
*
* <p>There is nothing in this class, but there will be many AST node types that have fields that
* are *any expression*. For those cases, having a field of this type will encompass all types of
* expressions such as binary expressions and literals that subclass this class.
*/
public abstract class Expr extends Node {
/** A Python expression spanning source locations [LEFT..RIGHT]. */
public Expr(Location left, Location right) {
super(left, right);
}
/**
* The type of the value that this expression evaluates to.
*
* <p>This field is always <tt>null</tt> after the parsing stage, but is populated by the
* typechecker in the semantic analysis stage.
*
* <p>After typechecking this field may be <tt>null</tt> only for expressions that cannot be
* assigned a type. In particular, {@link NoneLiteral} expressions will not have a typed
* assigned to them.
*/
@JsonInclude(JsonInclude.Include.NON_NULL)
private Type inferredType;
/** Set getInferredType() to TYPE, returning TYPE. */
public Type setInferredType(Type type) {
inferredType = type;
return type;
}
public Type getInferredType() {
return inferredType;
}
}
@@ -0,0 +1,21 @@
package chocopy.common.astnodes;
import chocopy.common.analysis.NodeAnalyzer;
import java_cup.runtime.ComplexSymbolFactory.Location;
/** Statements consisting of expressions. */
public final class ExprStmt extends Stmt {
/** The expression I evaluate. */
public final Expr expr;
/** The AST for EXPR spanning source locations [LEFT..RIGHT] in a statement context. */
public ExprStmt(Location left, Location right, Expr expr) {
super(left, right);
this.expr = expr;
}
public <T> T dispatch(NodeAnalyzer<T> analyzer) {
return analyzer.analyze(this);
}
}
@@ -0,0 +1,29 @@
package chocopy.common.astnodes;
import chocopy.common.analysis.NodeAnalyzer;
import java_cup.runtime.ComplexSymbolFactory.Location;
import java.util.List;
/** For statements. */
public class ForStmt extends Stmt {
/** Control variable. */
public final Identifier identifier;
/** Source of values of control statement. */
public final Expr iterable;
/** Repeated statements. */
public final List<Stmt> body;
/** The AST for for IDENTIFIER in ITERABLE: BODY spanning source locations [LEFT..RIGHT]. */
public ForStmt(
Location left, Location right, Identifier identifier, Expr iterable, List<Stmt> body) {
super(left, right);
this.identifier = identifier;
this.iterable = iterable;
this.body = body;
}
public <T> T dispatch(NodeAnalyzer<T> analyzer) {
return analyzer.analyze(this);
}
}
@@ -0,0 +1,50 @@
package chocopy.common.astnodes;
import chocopy.common.analysis.NodeAnalyzer;
import java_cup.runtime.ComplexSymbolFactory.Location;
import java.util.List;
/** Def statements. */
public class FuncDef extends Declaration {
/** Defined name. */
public final Identifier name;
/** Formal parameters. */
public final List<TypedVar> params;
/** Return type annotation. */
public final TypeAnnotation returnType;
/** Local-variable,inner-function, global, and nonlocal declarations. */
public final List<Declaration> declarations;
/** Other statements. */
public final List<Stmt> statements;
/**
* The AST for def NAME(PARAMS) -> RETURNTYPE: DECLARATIONS STATEMENTS spanning source locations
* [LEFT..RIGHT].
*/
public FuncDef(
Location left,
Location right,
Identifier name,
List<TypedVar> params,
TypeAnnotation returnType,
List<Declaration> declarations,
List<Stmt> statements) {
super(left, right);
this.name = name;
this.params = params;
this.returnType = returnType;
this.declarations = declarations;
this.statements = statements;
}
public <T> T dispatch(NodeAnalyzer<T> analyzer) {
return analyzer.analyze(this);
}
@Override
public Identifier getIdentifier() {
return this.name;
}
}
@@ -0,0 +1,26 @@
package chocopy.common.astnodes;
import chocopy.common.analysis.NodeAnalyzer;
import java_cup.runtime.ComplexSymbolFactory.Location;
/** Declaration of global variable. */
public class GlobalDecl extends Declaration {
/** The declared variable. */
public final Identifier variable;
/** The AST for the declaration global VARIABLE spanning source locations [LEFT..RIGHT]. */
public GlobalDecl(Location left, Location right, Identifier variable) {
super(left, right);
this.variable = variable;
}
public <T> T dispatch(NodeAnalyzer<T> analyzer) {
return analyzer.analyze(this);
}
@Override
public Identifier getIdentifier() {
return this.variable;
}
}
@@ -0,0 +1,24 @@
package chocopy.common.astnodes;
import chocopy.common.analysis.NodeAnalyzer;
import java_cup.runtime.ComplexSymbolFactory.Location;
/** A simple identifier. */
public class Identifier extends Expr {
/** Text of the identifier. */
public final String name;
/**
* An AST for the variable, method, or parameter named NAME, spanning source locations
* [LEFT..RIGHT].
*/
public Identifier(Location left, Location right, String name) {
super(left, right);
this.name = name;
}
public <T> T dispatch(NodeAnalyzer<T> analyzer) {
return analyzer.analyze(this);
}
}
@@ -0,0 +1,26 @@
package chocopy.common.astnodes;
import chocopy.common.analysis.NodeAnalyzer;
import java_cup.runtime.ComplexSymbolFactory.Location;
/** Conditional expressions. */
public class IfExpr extends Expr {
/** Boolean condition. */
public final Expr condition;
/** True branch. */
public final Expr thenExpr;
/** False branch. */
public final Expr elseExpr;
/** The AST for THENEXPR if CONDITION else ELSEEXPR spanning source locations [LEFT..RIGHT]. */
public IfExpr(Location left, Location right, Expr condition, Expr thenExpr, Expr elseExpr) {
super(left, right);
this.condition = condition;
this.thenExpr = thenExpr;
this.elseExpr = elseExpr;
}
public <T> T dispatch(NodeAnalyzer<T> analyzer) {
return analyzer.analyze(this);
}
}
@@ -0,0 +1,35 @@
package chocopy.common.astnodes;
import chocopy.common.analysis.NodeAnalyzer;
import java_cup.runtime.ComplexSymbolFactory.Location;
import java.util.List;
/** Conditional statement. */
public class IfStmt extends Stmt {
/** Test condition. */
public final Expr condition;
/** "True" branch. */
public final List<Stmt> thenBody;
/** "False" branch. */
public final List<Stmt> elseBody;
/**
* The AST for if CONDITION: THENBODY else: ELSEBODY spanning source locations [LEFT..RIGHT].
*/
public IfStmt(
Location left,
Location right,
Expr condition,
List<Stmt> thenBody,
List<Stmt> elseBody) {
super(left, right);
this.condition = condition;
this.thenBody = thenBody;
this.elseBody = elseBody;
}
public <T> T dispatch(NodeAnalyzer<T> analyzer) {
return analyzer.analyze(this);
}
}
@@ -0,0 +1,24 @@
package chocopy.common.astnodes;
import chocopy.common.analysis.NodeAnalyzer;
import java_cup.runtime.ComplexSymbolFactory.Location;
/** List-indexing expression. */
public class IndexExpr extends Expr {
/** Indexed list. */
public final Expr list;
/** Expression for index value. */
public final Expr index;
/** The AST for LIST[INDEX]. spanning source locations [LEFT..RIGHT]. */
public IndexExpr(Location left, Location right, Expr list, Expr index) {
super(left, right);
this.list = list;
this.index = index;
}
public <T> T dispatch(NodeAnalyzer<T> analyzer) {
return analyzer.analyze(this);
}
}
@@ -0,0 +1,21 @@
package chocopy.common.astnodes;
import chocopy.common.analysis.NodeAnalyzer;
import java_cup.runtime.ComplexSymbolFactory.Location;
/** Integer numerals. */
public final class IntegerLiteral extends Literal {
/** Value denoted. */
public final int value;
/** The AST for the literal VALUE, spanning source locations [LEFT..RIGHT]. */
public IntegerLiteral(Location left, Location right, int value) {
super(left, right);
this.value = value;
}
public <T> T dispatch(NodeAnalyzer<T> analyzer) {
return analyzer.analyze(this);
}
}
@@ -0,0 +1,23 @@
package chocopy.common.astnodes;
import chocopy.common.analysis.NodeAnalyzer;
import java_cup.runtime.ComplexSymbolFactory.Location;
import java.util.List;
/** List displays. */
public final class ListExpr extends Expr {
/** List of element expressions. */
public final List<Expr> elements;
/** The AST for [ ELEMENTS ]. spanning source locations [LEFT..RIGHT]. */
public ListExpr(Location left, Location right, List<Expr> elements) {
super(left, right);
this.elements = elements;
}
public <T> T dispatch(NodeAnalyzer<T> analyzer) {
return analyzer.analyze(this);
}
}
@@ -0,0 +1,21 @@
package chocopy.common.astnodes;
import chocopy.common.analysis.NodeAnalyzer;
import java_cup.runtime.ComplexSymbolFactory.Location;
/** Type denotation for a list type. */
public final class ListType extends TypeAnnotation {
/** The element of list element. */
public final TypeAnnotation elementType;
/** The AST for the type annotation [ ELEMENTTYPE ]. spanning source locations [LEFT..RIGHT]. */
public ListType(Location left, Location right, TypeAnnotation elementType) {
super(left, right);
this.elementType = elementType;
}
public <T> T dispatch(NodeAnalyzer<T> analyzer) {
return analyzer.analyze(this);
}
}
@@ -0,0 +1,16 @@
package chocopy.common.astnodes;
import java_cup.runtime.ComplexSymbolFactory.Location;
/**
* Base of all the literal nodes.
*
* <p>There is nothing in this class, but it is useful to isolate expressions that are constant
* literals.
*/
public abstract class Literal extends Expr {
/** A literal spanning source locations [LEFT..RIGHT]. */
public Literal(Location left, Location right) {
super(left, right);
}
}
@@ -0,0 +1,24 @@
package chocopy.common.astnodes;
import chocopy.common.analysis.NodeAnalyzer;
import java_cup.runtime.ComplexSymbolFactory.Location;
/** Attribute accessor. */
public class MemberExpr extends Expr {
/** Object selected from. */
public final Expr object;
/** Name of attribute (instance variable or method). */
public final Identifier member;
/** The AST for OBJECT.MEMBER. spanning source locations [LEFT..RIGHT]. */
public MemberExpr(Location left, Location right, Expr object, Identifier member) {
super(left, right);
this.object = object;
this.member = member;
}
public <T> T dispatch(NodeAnalyzer<T> analyzer) {
return analyzer.analyze(this);
}
}
@@ -0,0 +1,26 @@
package chocopy.common.astnodes;
import chocopy.common.analysis.NodeAnalyzer;
import java_cup.runtime.ComplexSymbolFactory.Location;
import java.util.List;
/** Method calls. */
public class MethodCallExpr extends Expr {
/** Expression for the bound method to be called. */
public final MemberExpr method;
/** Actual parameters. */
public final List<Expr> args;
/** The AST for METHOD(ARGS). spanning source locations [LEFT..RIGHT]. */
public MethodCallExpr(Location left, Location right, MemberExpr method, List<Expr> args) {
super(left, right);
this.method = method;
this.args = args;
}
public <T> T dispatch(NodeAnalyzer<T> analyzer) {
return analyzer.analyze(this);
}
}
@@ -0,0 +1,176 @@
package chocopy.common.astnodes;
import chocopy.common.analysis.NodeAnalyzer;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.module.paramnames.ParameterNamesModule;
import java_cup.runtime.ComplexSymbolFactory.Location;
import java.io.IOException;
/**
* Root of the AST class hierarchy. Every node has a left and right location, indicating the start
* and end of the represented construct in the source text.
*
* <p>Every node can be marked with an error message, which serves two purposes: 1. It indicates
* that an error message has been issued for this Node, allowing tne program to reduce cascades of
* error messages. 2. It aids in debugging by making it convenient to see which Nodes have caused an
* error.
*/
@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.EXISTING_PROPERTY,
property = "kind")
/* List of all concrete subclasses of Node. */
@JsonSubTypes({
@JsonSubTypes.Type(AssignStmt.class),
@JsonSubTypes.Type(BinaryExpr.class),
@JsonSubTypes.Type(BooleanLiteral.class),
@JsonSubTypes.Type(CallExpr.class),
@JsonSubTypes.Type(ClassDef.class),
@JsonSubTypes.Type(ClassType.class),
@JsonSubTypes.Type(CompilerError.class),
@JsonSubTypes.Type(Errors.class),
@JsonSubTypes.Type(ExprStmt.class),
@JsonSubTypes.Type(ForStmt.class),
@JsonSubTypes.Type(FuncDef.class),
@JsonSubTypes.Type(GlobalDecl.class),
@JsonSubTypes.Type(Identifier.class),
@JsonSubTypes.Type(IfExpr.class),
@JsonSubTypes.Type(IfStmt.class),
@JsonSubTypes.Type(IndexExpr.class),
@JsonSubTypes.Type(IntegerLiteral.class),
@JsonSubTypes.Type(ListExpr.class),
@JsonSubTypes.Type(ListType.class),
@JsonSubTypes.Type(MemberExpr.class),
@JsonSubTypes.Type(MethodCallExpr.class),
@JsonSubTypes.Type(NoneLiteral.class),
@JsonSubTypes.Type(NonLocalDecl.class),
@JsonSubTypes.Type(Program.class),
@JsonSubTypes.Type(ReturnStmt.class),
@JsonSubTypes.Type(StringLiteral.class),
@JsonSubTypes.Type(TypedVar.class),
@JsonSubTypes.Type(UnaryExpr.class),
@JsonSubTypes.Type(VarDef.class),
@JsonSubTypes.Type(WhileStmt.class),
})
public abstract class Node {
/** Node-type indicator for JSON form. */
public final String kind;
/**
* Source position information: 0: line number of start, 1: column number of start, 2: line
* number of end, 3: column number of end.
*/
private final int[] location = new int[4];
/**
* First error message "blamed" on this Node. When non-null, indicates that an error has been
* found in this Node.
*/
@JsonInclude(JsonInclude.Include.NON_EMPTY)
private String errorMsg;
/** A Node corresponding to source text between LEFT and RIGHT. */
public Node(Location left, Location right) {
if (left != null) {
location[0] = left.getLine();
location[1] = left.getColumn();
}
if (right != null) {
location[2] = right.getLine();
location[3] = right.getColumn();
}
this.kind = getClass().getSimpleName();
this.errorMsg = null;
}
/**
* Return my source location as { <first line>, <first column>, <last line>, <last column> }.
* Result should not be modified, and contents will change after setLocation().
*/
public int[] getLocation() {
return location;
}
/** Copy LOCATION as getLocation(). */
public void setLocation(final int[] location) {
System.arraycopy(location, 0, this.location, 0, 4);
}
public String getErrorMsg() {
return errorMsg;
}
public void setErrorMsg(String msg) {
this.errorMsg = msg;
}
/** Return true iff I have been marked with an error message. */
@JsonIgnore
public boolean hasError() {
return this.errorMsg != null;
}
/**
* Invoke ANALYZER on me as a node of static type T. See the comment on NodeAnalyzer. Returns
* modified Node.
*/
public abstract <T> T dispatch(NodeAnalyzer<T> analyzer);
/** Print out the AST in JSON format. */
@Override
public String toString() {
try {
return toJSON();
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
/** Return a serialization of this node in JSON format. */
public String toJSON() throws JsonProcessingException {
return mapper.writeValueAsString(this);
}
/** Mapper to-and-from serialized JSON. */
private static final ObjectMapper mapper = new ObjectMapper();
static {
mapper.enable(SerializationFeature.INDENT_OUTPUT);
mapper.registerModule(new ParameterNamesModule());
}
/** Returns a T from JSON, a JSON-serialized T value with class CLAS. */
public static <T> T fromJSON(String json, Class<T> clas) throws IOException {
return mapper.readValue(json, clas);
}
/**
* Returns the result of converting JSON, a JSon-serialization of a Node value, into the value
* it serializes.
*/
public static Node fromJSON(String json) throws IOException {
return fromJSON(json, Node.class);
}
/**
* Returns the result of converting TREE to the value of type T that it represents, where CLAS
* reflects T.
*/
public static <T> T fromJSON(JsonNode tree, Class<T> clas) throws IOException {
return mapper.treeToValue(tree, clas);
}
/** Returns the translation of serialized value SRC into the corresponding JSON tree. */
public static JsonNode readTree(String src) throws IOException {
return mapper.readTree(src);
}
}
@@ -0,0 +1,26 @@
package chocopy.common.astnodes;
import chocopy.common.analysis.NodeAnalyzer;
import java_cup.runtime.ComplexSymbolFactory.Location;
/** Nonlocal declaration. */
public class NonLocalDecl extends Declaration {
/** Name of identifier being declared. */
public final Identifier variable;
/** The AST for nonlocal VARIABLE spanning source locations [LEFT..RIGHT]. */
public NonLocalDecl(Location left, Location right, Identifier variable) {
super(left, right);
this.variable = variable;
}
public <T> T dispatch(NodeAnalyzer<T> analyzer) {
return analyzer.analyze(this);
}
@Override
public Identifier getIdentifier() {
return this.variable;
}
}
@@ -0,0 +1,17 @@
package chocopy.common.astnodes;
import chocopy.common.analysis.NodeAnalyzer;
import java_cup.runtime.ComplexSymbolFactory.Location;
/** The expression 'None'. */
public final class NoneLiteral extends Literal {
/** The AST for None, spanning source locations [LEFT..RIGHT]. */
public NoneLiteral(Location left, Location right) {
super(left, right);
}
public <T> T dispatch(NodeAnalyzer<T> analyzer) {
return analyzer.analyze(this);
}
}
@@ -0,0 +1,56 @@
package chocopy.common.astnodes;
import chocopy.common.analysis.NodeAnalyzer;
import com.fasterxml.jackson.annotation.JsonIgnore;
import java_cup.runtime.ComplexSymbolFactory.Location;
import java.util.ArrayList;
import java.util.List;
/** An entire ChocoPy program. */
public class Program extends Node {
/** Initial variable, class, and function declarations. */
public final List<Declaration> declarations;
/** Trailing statements. */
public final List<Stmt> statements;
/** Accumulated errors. */
public final Errors errors;
/**
* The AST for the program DECLARATIONS STATEMENTS spanning source locations [LEFT..RIGHT].
*
* <p>ERRORS is the container for all error messages applying to the program.
*/
public Program(
Location left,
Location right,
List<Declaration> declarations,
List<Stmt> statements,
Errors errors) {
super(left, right);
this.declarations = declarations;
this.statements = statements;
if (errors == null) {
this.errors = new Errors(new ArrayList<>());
} else {
this.errors = errors;
}
}
public <T> T dispatch(NodeAnalyzer<T> analyzer) {
return analyzer.analyze(this);
}
/** Returns true iff there is at least one error in the program. */
@JsonIgnore
public boolean hasErrors() {
return errors.hasErrors();
}
/** A convenience method returning the list of all CompilerErrors for this program. */
@JsonIgnore
public List<CompilerError> getErrorList() {
return errors.errors;
}
}
@@ -0,0 +1,21 @@
package chocopy.common.astnodes;
import chocopy.common.analysis.NodeAnalyzer;
import java_cup.runtime.ComplexSymbolFactory.Location;
/** Return from function. */
public class ReturnStmt extends Stmt {
/** Returned value. */
public final Expr value;
/** The AST for return VALUE spanning source locations [LEFT..RIGHT]. */
public ReturnStmt(Location left, Location right, Expr value) {
super(left, right);
this.value = value;
}
public <T> T dispatch(NodeAnalyzer<T> analyzer) {
return analyzer.analyze(this);
}
}
@@ -0,0 +1,18 @@
package chocopy.common.astnodes;
import java_cup.runtime.ComplexSymbolFactory.Location;
/**
* Base of all AST nodes representing statements.
*
* <p>There is nothing in this class, but there will be some AST node types that have fields that
* are *any statement* or a list of statements. For those cases, having a field of this type will
* encompass all types of statements such as expression statements, if statements, while statements,
* etc.
*/
public abstract class Stmt extends Node {
/** A statement spanning source locations [LEFT..RIGHT]. */
public Stmt(Location left, Location right) {
super(left, right);
}
}
@@ -0,0 +1,21 @@
package chocopy.common.astnodes;
import chocopy.common.analysis.NodeAnalyzer;
import java_cup.runtime.ComplexSymbolFactory.Location;
/** String constants. */
public final class StringLiteral extends Literal {
/** Contents of the literal, not including quotation marks. */
public final String value;
/** The AST for a string literal containing VALUE, spanning source locations [LEFT..RIGHT]. */
public StringLiteral(Location left, Location right, String value) {
super(left, right);
this.value = value;
}
public <T> T dispatch(NodeAnalyzer<T> analyzer) {
return analyzer.analyze(this);
}
}
@@ -0,0 +1,11 @@
package chocopy.common.astnodes;
import java_cup.runtime.ComplexSymbolFactory.Location;
/** Base of all AST nodes representing type annotations (list or class types. */
public abstract class TypeAnnotation extends Node {
/** An annotation spanning source locations [LEFT..RIGHT]. */
public TypeAnnotation(Location left, Location right) {
super(left, right);
}
}
@@ -0,0 +1,24 @@
package chocopy.common.astnodes;
import chocopy.common.analysis.NodeAnalyzer;
import java_cup.runtime.ComplexSymbolFactory.Location;
/** An identifier with attached type annotation. */
public class TypedVar extends Node {
/** The typed identifier. */
public final Identifier identifier;
/** The declared type. */
public final TypeAnnotation type;
/** The AST for IDENTIFIER : TYPE. spanning source locations [LEFT..RIGHT]. */
public TypedVar(Location left, Location right, Identifier identifier, TypeAnnotation type) {
super(left, right);
this.identifier = identifier;
this.type = type;
}
public <T> T dispatch(NodeAnalyzer<T> analyzer) {
return analyzer.analyze(this);
}
}
@@ -0,0 +1,24 @@
package chocopy.common.astnodes;
import chocopy.common.analysis.NodeAnalyzer;
import java_cup.runtime.ComplexSymbolFactory.Location;
/** An expression applying a unary operator. */
public class UnaryExpr extends Expr {
/** The text representation of the operator. */
public final String operator;
/** The operand to which it is applied. */
public final Expr operand;
/** The AST for OPERATOR OPERAND spanning source locations [LEFT..RIGHT]. */
public UnaryExpr(Location left, Location right, String operator, Expr operand) {
super(left, right);
this.operator = operator;
this.operand = operand;
}
public <T> T dispatch(NodeAnalyzer<T> analyzer) {
return analyzer.analyze(this);
}
}
@@ -0,0 +1,32 @@
package chocopy.common.astnodes;
import chocopy.common.analysis.NodeAnalyzer;
import java_cup.runtime.ComplexSymbolFactory.Location;
/** A declaration of a variable (i.e., with type annotation). */
public class VarDef extends Declaration {
/** The variable and its assigned type. */
public final TypedVar var;
/** The initial value assigned. */
public final Literal value;
/**
* The AST for VAR = VALUE where VAR has a type annotation, and spanning source locations
* [LEFT..RIGHT].
*/
public VarDef(Location left, Location right, TypedVar var, Literal value) {
super(left, right);
this.var = var;
this.value = value;
}
public <T> T dispatch(NodeAnalyzer<T> analyzer) {
return analyzer.analyze(this);
}
/** The identifier defined by this declaration. */
@Override
public Identifier getIdentifier() {
return this.var.identifier;
}
}
@@ -0,0 +1,25 @@
package chocopy.common.astnodes;
import chocopy.common.analysis.NodeAnalyzer;
import java_cup.runtime.ComplexSymbolFactory.Location;
import java.util.List;
/** Indefinite repetition construct. */
public class WhileStmt extends Stmt {
/** Test for whether to continue. */
public final Expr condition;
/** Loop body. */
public final List<Stmt> body;
/** The AST for while CONDITION: BODY spanning source locations [LEFT..RIGHT]. */
public WhileStmt(Location left, Location right, Expr condition, List<Stmt> body) {
super(left, right);
this.condition = condition;
this.body = body;
}
public <T> T dispatch(NodeAnalyzer<T> analyzer) {
return analyzer.analyze(this);
}
}
@@ -0,0 +1,16 @@
package chocopy.common.codegen;
import chocopy.common.analysis.types.ValueType;
import chocopy.common.astnodes.Literal;
/** Information concerning an instance variable. */
public class AttrInfo extends VarInfo {
/**
* A descriptor for an attribute named ATTRNAME of type VARTYPE whose initial value, if any, is
* a constant specified by INITIALVALUE (it is otherwise null).
*/
public AttrInfo(String attrName, ValueType varType, Literal initialValue) {
super(attrName, varType, initialValue);
}
}
@@ -0,0 +1,123 @@
package chocopy.common.codegen;
import java.util.ArrayList;
import java.util.List;
/** Information for code generation a class. */
public class ClassInfo extends SymbolInfo {
/** Name of class. */
protected final String className;
/** Information about instance variables of the class. */
public final List<AttrInfo> attributes;
/** Information about methods of the class. */
public final List<FuncInfo> methods;
/**
* Tag indicating type of value: 0: (reserved) 1: int 2: bool 3: str -1: [T] for any T >3:
* User-defined types.
*/
protected final int typeTag;
/** Label of area containing initial instance values. */
protected final Label prototypeLabel;
/** Label of area containing method-dispatching table. */
protected Label dispatchTableLabel;
/**
* A descriptor for a class named CLASSNAME identified by runtime tag TYPETAG, and having the
* class denoted by SUPERCLASSINFO as its superclass. The latter is null iff the class is
* object.
*/
public ClassInfo(String className, int typeTag, ClassInfo superClassInfo) {
this.className = className;
this.typeTag = typeTag;
prototypeLabel = new Label(String.format("$%s$%s", className, "prototype"));
dispatchTableLabel = new Label(String.format("$%s$%s", className, "dispatchTable"));
attributes = new ArrayList<>();
methods = new ArrayList<>();
if (superClassInfo != null) {
attributes.addAll(superClassInfo.attributes);
methods.addAll(superClassInfo.methods);
}
}
/** Add an attribute described by ATTRINFO. */
public void addAttribute(AttrInfo attrInfo) {
this.attributes.add(attrInfo);
}
/**
* Add a method described by FUNCINFO, overriding any inherited method of that name if
* necessary.
*/
public void addMethod(FuncInfo funcInfo) {
String methodName = funcInfo.getBaseName();
int idx = this.getMethodIndex(methodName);
if (idx >= 0) {
this.methods.set(idx, funcInfo);
} else {
this.methods.add(funcInfo);
}
}
/** Return my type tag. */
public int getTypeTag() {
return typeTag;
}
/** Returns the address of this class's prototype object (a label). */
public Label getPrototypeLabel() {
return prototypeLabel;
}
/** Returns the address of this class's dispatch table (a label). */
public Label getDispatchTableLabel() {
return dispatchTableLabel;
}
/**
* Returns the index of the attribute named ATTRNAME in order of definition.
*
* <p>This index takes into account inherited attribute and returns the index of an attribute as
* a slot index in its object layout (excluding the object header). Attributes are numbered from
* 0; the result is an index, and not a byte offset.
*/
public int getAttributeIndex(String attrName) {
for (int i = 0; i < attributes.size(); i++) {
if (attributes.get(i).getVarName().equals(attrName)) {
return i;
}
}
return -1;
}
/**
* Returns the index of the method named METHODNAME in order of definition.
*
* <p>This index takes into account inherited and overridden methods and returns the index of
* the method as a slot number (not a byte offset) in the dispatch table.
*/
public int getMethodIndex(String methodName) {
for (int i = 0; i < methods.size(); i++) {
if (methods.get(i).getBaseName().equals(methodName)) {
return i;
}
}
return -1;
}
public String getClassName() {
return className;
}
/** Returns the list of attributes of this class, in order of the object's layout. */
public List<AttrInfo> getAttributes() {
return attributes;
}
/** Returns the list of methods of this class, in order of the object's dispatch table. */
public List<FuncInfo> getMethods() {
return methods;
}
}
@@ -0,0 +1,880 @@
package chocopy.common.codegen;
import chocopy.common.analysis.AbstractNodeAnalyzer;
import chocopy.common.analysis.SymbolTable;
import chocopy.common.analysis.types.Type;
import chocopy.common.analysis.types.ValueType;
import chocopy.common.astnodes.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static chocopy.common.Utils.*;
import static chocopy.common.codegen.RiscVBackend.Register.*;
/**
* The code generator for a ChocoPy program.
*
* <p>This class implements logic to analyze all declarations in a program and create descriptors
* for classes, functions, methods, variables (global and local), and attributes. This logic also
* builds symbol tables for globals and individual functions.
*
* <p>This class also implements logic to emit global variables, object prototypes and dispatch
* tables, as well as int/str/bool constants.
*
* <p>However, this class is abstract because it does not implement logic for emitting executable
* code in bodies of user-defined functions as well as in top-level statements. This class should be
* extended with implementations for such logic.
*
* <p>All non-public members of this class are `protected`, and can be overridden by sub-classes to
* extend change functionality.
*
* <p>The SymbolInfo classes can also be overridden. If say you want to use your own extended
* FuncInfo called MyFuncInfo (that extends FuncInfo), then override the makeFuncInfo() method of
* this class to `return new MyFuncInfo(...)` instead. This is probably not needed, though.
*/
public abstract class CodeGenBase {
/** The location of the text resources containing common library code. */
protected static final String LIBRARY_CODE_DIR = "chocopy/common/";
/** The backend that emits assembly. */
protected final RiscVBackend backend;
/** Convenience variable: the word size for the current back end. */
protected final int wordSize;
/** A counter for generating unique class type tags. */
protected int nextTypeTag = 0;
/** A counter used to generate unique local label names. */
protected int nextLabelSuffix = 0;
/**
* Predefined classes. The list "class" is a fake class; we use it only to emit a prototype
* object for empty lists.
*/
protected ClassInfo objectClass, intClass, boolClass, strClass, listClass;
/** Predefined functions. */
protected FuncInfo printFunc, lenFunc, inputFunc;
/** A list of global variables, whose initial values are emitted in the backend. */
protected final List<GlobalVarInfo> globalVars = new ArrayList<>();
/**
* A list of program classes, whose prototype objects and dispatch tables are emitted in the
* backend.
*/
protected final List<ClassInfo> classes = new ArrayList<>();
/**
* A list of functions (including methods and nested functions) whose bodies are emitted in the
* backend.
*/
protected final List<FuncInfo> functions = new ArrayList<>();
/** Label for built-in routine: alloc. */
protected final Label objectAllocLabel = new Label("alloc");
/** Label for built-in routine: alloc2. */
protected final Label objectAllocResizeLabel = new Label("alloc2");
/** Label for built-in routine: abort. */
protected final Label abortLabel = new Label("abort");
/** Label for built-in routine: heap.init. */
protected final Label heapInitLabel = new Label("heap.init");
/** Error codes. */
protected final int ERROR_ARG = 1,
ERROR_DIV_ZERO = 2,
ERROR_OOB = 3,
ERROR_NONE = 4,
ERROR_OOM = 5,
ERROR_NYI = 6;
/** Size of heap memory. */
protected final int HEAP_SIZE_BYTES = 1024 * 1024 * 32;
/** Ecall numbers for intrinsic routines. */
protected final int EXIT_ECALL = 10,
EXIT2_ECALL = 17,
PRINT_STRING_ECALL = 4,
PRINT_CHAR_ECALL = 11,
PRINT_INT_ECALL = 1,
READ_STRING_ECALL = 8,
FILL_LINE_BUFFER__ECALL = 18,
SBRK_ECALL = 9;
/**
* The symbol table that maps global names to information about the bound global variables,
* global functions, or classes.
*/
protected final SymbolTable<SymbolInfo> globalSymbols = new SymbolTable<>();
/** A utility for caching constants and generating labels for constants. */
protected final Constants constants = new Constants();
/** The object header size, in words (includes type tag, size, and dispatch table pointer). */
public static final int HEADER_SIZE = 3;
/**
* Initializes a code generator for ChocoPy that uses BACKEND to emit assembly code.
*
* <p>The constructor creates Info objects for predefined functions, classes, methods, and
* built-in routines.
*/
public CodeGenBase(RiscVBackend backend) {
this.backend = backend;
wordSize = backend.getWordSize();
initClasses();
initFunctions();
initAsmConstants();
}
/** Return a fresh type tag. */
protected int getNextTypeTag() {
return nextTypeTag++;
}
/** Returns the next unique label suffix. */
protected int getNextLabelSuffix() {
return nextLabelSuffix++;
}
/**
* Return a fresh label.
*
* <p>This label is guaranteed to be unique amongst labels generated by invoking this method.
* All such labels have a prefix of `label_`.
*
* <p>This is useful to generate local labels in function bodies (e.g. for targets of jumps),
* where the name does not matter in general.
*/
protected Label generateLocalLabel() {
return new Label(String.format("label_%d", getNextLabelSuffix()));
}
/**
* Generates assembly code for PROGRAM.
*
* <p>This is the main driver that calls internal methods for emitting DATA section (globals,
* constants, prototypes, etc) as well as the the CODE section (predefined functions, built-in
* routines, and user-defined functions).
*/
public void generate(Program program) {
analyzeProgram(program);
backend.startData();
for (ClassInfo classInfo : this.classes) {
emitPrototype(classInfo);
}
for (ClassInfo classInfo : this.classes) {
emitDispatchTable(classInfo);
}
for (GlobalVarInfo global : this.globalVars) {
backend.emitGlobalLabel(global.getLabel());
emitConstant(
global.getInitialValue(),
global.getVarType(),
String.format("Initial value of global var: %s", global.getVarName()));
}
backend.startCode();
Label mainLabel = new Label("main");
backend.emitGlobalLabel(mainLabel);
backend.emitLUI(A0, HEAP_SIZE_BYTES >> 12, "Initialize heap size (in multiples of 4KB)");
backend.emitADD(S11, S11, A0, "Save heap size");
backend.emitJAL(heapInitLabel, "Call heap.init routine");
backend.emitMV(GP, A0, "Initialize heap pointer");
backend.emitMV(S10, GP, "Set beginning of heap");
backend.emitADD(S11, S10, S11, "Set end of heap (= start of heap + heap size)");
backend.emitMV(RA, ZERO, "No normal return from main program.");
backend.emitMV(FP, ZERO, "No preceding frame.");
emitTopLevel(program.statements);
for (FuncInfo funcInfo : this.functions) {
funcInfo.emitBody();
}
emitStdFunc("alloc");
emitStdFunc("alloc2");
emitStdFunc("abort");
emitStdFunc("heap.init");
emitCustomCode();
backend.startData();
emitConstants();
}
/** Create descriptors and symbols for builtin classes and methods. */
protected void initClasses() {
FuncInfo objectInit =
makeFuncInfo(
"object.__init__",
0,
Type.NONE_TYPE,
globalSymbols,
null,
this::emitStdFunc);
objectInit.addParam(makeStackVarInfo("self", Type.OBJECT_TYPE, null, objectInit));
functions.add(objectInit);
objectClass = makeClassInfo("object", getNextTypeTag(), null);
objectClass.addMethod(objectInit);
classes.add(objectClass);
globalSymbols.put(objectClass.getClassName(), objectClass);
intClass = makeClassInfo("int", getNextTypeTag(), objectClass);
intClass.addAttribute(makeAttrInfo("__int__", null, null));
classes.add(intClass);
globalSymbols.put(intClass.getClassName(), intClass);
boolClass = makeClassInfo("bool", getNextTypeTag(), objectClass);
boolClass.addAttribute(makeAttrInfo("__bool__", null, null));
classes.add(boolClass);
globalSymbols.put(boolClass.getClassName(), boolClass);
strClass = makeClassInfo("str", getNextTypeTag(), objectClass);
strClass.addAttribute(
makeAttrInfo("__len__", Type.INT_TYPE, new IntegerLiteral(null, null, 0)));
strClass.addAttribute(makeAttrInfo("__str__", null, null));
classes.add(strClass);
globalSymbols.put(strClass.getClassName(), strClass);
listClass = makeClassInfo(".list", -1, objectClass);
listClass.addAttribute(
makeAttrInfo("__len__", Type.INT_TYPE, new IntegerLiteral(null, null, 0)));
classes.add(listClass);
listClass.dispatchTableLabel = null;
}
/** Create descriptors and symbols for builtin functions. */
protected void initFunctions() {
printFunc =
makeFuncInfo("print", 0, Type.NONE_TYPE, globalSymbols, null, this::emitStdFunc);
printFunc.addParam(makeStackVarInfo("arg", Type.OBJECT_TYPE, null, printFunc));
functions.add(printFunc);
globalSymbols.put(printFunc.getBaseName(), printFunc);
lenFunc = makeFuncInfo("len", 0, Type.INT_TYPE, globalSymbols, null, this::emitStdFunc);
lenFunc.addParam(makeStackVarInfo("arg", Type.OBJECT_TYPE, null, lenFunc));
functions.add(lenFunc);
globalSymbols.put(lenFunc.getBaseName(), lenFunc);
inputFunc = makeFuncInfo("input", 0, Type.STR_TYPE, globalSymbols, null, this::emitStdFunc);
functions.add(inputFunc);
globalSymbols.put(inputFunc.getBaseName(), inputFunc);
}
/* Symbolic assembler constants defined here (to add others, override
* initAsmConstants in an extension of CodeGenBase):
* ecalls:
* @sbrk
* @fill_line_buffer
* @read_string
* @print_string
* @print_char
* @print_int
* @exit2
* Exit codes:
* @error_div_zero: Division by 0.
* @error_arg: Bad argument.
* @error_oob: Out of bounds.
* @error_none: Attempt to access attribute of None.
* @error_oom: Out of memory.
* @error_nyi: Unimplemented operation.
* Data-structure byte offsets:
* @.__obj_size__: Offset of size of object.
* @.__len__: Offset of length in chars or words.
* @.__str__: Offset of string data.
* @.__elts__: Offset of first list item.
* @.__int__: Offset of integer value.
* @.__bool__: Offset of boolean (1/0) value.
*/
/** Define @-constants to be used in assembly code. */
protected void initAsmConstants() {
backend.defineSym("sbrk", SBRK_ECALL);
backend.defineSym("print_string", PRINT_STRING_ECALL);
backend.defineSym("print_char", PRINT_CHAR_ECALL);
backend.defineSym("print_int", PRINT_INT_ECALL);
backend.defineSym("exit2", EXIT2_ECALL);
backend.defineSym("read_string", READ_STRING_ECALL);
backend.defineSym("fill_line_buffer", FILL_LINE_BUFFER__ECALL);
backend.defineSym(".__obj_size__", 4);
backend.defineSym(".__len__", 12);
backend.defineSym(".__int__", 12);
backend.defineSym(".__bool__", 12);
backend.defineSym(".__str__", 16);
backend.defineSym(".__elts__", 16);
backend.defineSym("error_div_zero", ERROR_DIV_ZERO);
backend.defineSym("error_arg", ERROR_ARG);
backend.defineSym("error_oob", ERROR_OOB);
backend.defineSym("error_none", ERROR_NONE);
backend.defineSym("error_oom", ERROR_OOM);
backend.defineSym("error_nyi", ERROR_NYI);
}
/*-----------------------------------------------------------*/
/* */
/* FACTORY METHODS TO CREATE INFO OBJECTS */
/* */
/*-----------------------------------------------------------*/
/**
* A factory method that returns a descriptor for function or method FUNCNAME returning type
* RETURNTYPE at nesting level DEPTH in the region corresponding to PARENTSYMBOLTABLE.
*
* <p>PARENTFUNCINFO is a descriptor of the enclosing function and is null for global functions
* and methods.
*
* <p>EMITTER is a method that emits the function's body (usually a generic emitter for
* user-defined functions/methods, and a special emitter for pre-defined functions/methods).
*
* <p>Sub-classes of CodeGenBase can override this method if they wish to use a sub-class of
* FuncInfo with more functionality.
*/
protected FuncInfo makeFuncInfo(
String funcName,
int depth,
ValueType returnType,
SymbolTable<SymbolInfo> parentSymbolTable,
FuncInfo parentFuncInfo,
Consumer<FuncInfo> emitter) {
return new FuncInfo(
funcName, depth, returnType, parentSymbolTable, parentFuncInfo, emitter);
}
/**
* Return a descriptor for a class named CLASSNAME having type tag TYPETAG and superclass
* SUPERCLASSINFO (null for `object' only).
*
* <p>Sub-classes of CodeGenBase can override this method if they wish to use a sub-class of
* ClassInfo with more functionality.
*/
public ClassInfo makeClassInfo(String className, int typeTag, ClassInfo superClassInfo) {
return new ClassInfo(className, typeTag, superClassInfo);
}
/**
* A factory method that returns a descriptor for an attribute named ATTRNAME of type ATTRTYPE
* and with an initial value specified by INITIALVALUE, which may be null to indicate a default
* initialization.
*
* <p>Sub-classes of CodeGenBase can override this method if they wish to use a sub-class of
* AttrInfo with more functionality.
*/
public AttrInfo makeAttrInfo(String attrName, ValueType attrType, Literal initialValue) {
return new AttrInfo(attrName, attrType, initialValue);
}
/**
* A factory method that returns a descriptor for a local variable or parameter named VARNAME of
* type VARTYPE, whose initial value is specified by INITIALVALUE (if non-null) and which is
* defined immediately within the function given by FUNCINFO.
*
* <p>These variables are allocated on the stack in activation frames.
*
* <p>Sub-classes of CodeGenBase can override this method if they wish to use a sub-class of
* StackVarInfo with more functionality.
*/
public StackVarInfo makeStackVarInfo(
String varName, ValueType varType, Literal initialValue, FuncInfo funcInfo) {
return new StackVarInfo(varName, varType, initialValue, funcInfo);
}
/**
* A factory method that returns a descriptor for a global variable with name VARNAME and type
* VARTYPE, whose initial value is specified by INITIALVALUE (if non-null).
*
* <p>Sub-classes of CodeGenBase can override this method if they wish to use a sub-class of
* GlobalVarInfo with more functionality.
*/
public GlobalVarInfo makeGlobalVarInfo(
String varName, ValueType varType, Literal initialValue) {
return new GlobalVarInfo(varName, varType, initialValue);
}
/*-----------------------------------------------------------*
* *
* ANALYSIS OF AST INTO INFO OBJECTS *
* (Students can ignore these methods as all the work has *
* been done and does not need to be modified/extended) *
* *
*-----------------------------------------------------------*/
/** Analyze PROGRAM, creating Info objects for all symbols. Populate the global symbol table. */
protected void analyzeProgram(Program program) {
/* Proceed in phases:
* 1. Analyze all global variable declarations.
* Do this first so that global variables are in the symbol
* table before we encounter `global x` declarations.
* 2. Analyze classes and global functions now that global variables
* are in the symbol table.
*/
for (Declaration decl : program.declarations) {
if (decl instanceof VarDef) {
VarDef varDef = (VarDef) decl;
ValueType varType = ValueType.annotationToValueType(varDef.var.type);
GlobalVarInfo globalVar =
makeGlobalVarInfo(varDef.var.identifier.name, varType, varDef.value);
this.globalVars.add(globalVar);
this.globalSymbols.put(globalVar.getVarName(), globalVar);
}
}
for (Declaration decl : program.declarations) {
if (decl instanceof ClassDef) {
ClassDef classDef = (ClassDef) decl;
ClassInfo classInfo = analyzeClass(classDef);
this.classes.add(classInfo);
this.globalSymbols.put(classInfo.getClassName(), classInfo);
} else if (decl instanceof FuncDef) {
FuncDef funcDef = (FuncDef) decl;
FuncInfo funcInfo = analyzeFunction(null, funcDef, 0, globalSymbols, null);
this.functions.add(funcInfo);
this.globalSymbols.put(funcInfo.getBaseName(), funcInfo);
}
}
}
/**
* Analyze a class definition CLASSDEF and return the resulting Info object. Also creates Info
* objects for attributes/methods and stores them in the ClassInfo. Methods are recursively
* analyzed using analyzeFunction().
*/
protected ClassInfo analyzeClass(ClassDef classDef) {
String className = classDef.name.name;
String superClassName = classDef.superClass.name;
SymbolInfo superSymbolInfo = globalSymbols.get(superClassName);
assert superSymbolInfo instanceof ClassInfo
: "Semantic analysis should ensure that super-class is defined";
ClassInfo superClassInfo = (ClassInfo) superSymbolInfo;
ClassInfo classInfo = makeClassInfo(className, getNextTypeTag(), superClassInfo);
for (Declaration decl : classDef.declarations) {
if (decl instanceof VarDef) {
VarDef attrDef = (VarDef) decl;
ValueType attrType = ValueType.annotationToValueType(attrDef.var.type);
AttrInfo attrInfo =
makeAttrInfo(attrDef.var.identifier.name, attrType, attrDef.value);
classInfo.addAttribute(attrInfo);
} else if (decl instanceof FuncDef) {
FuncDef funcDef = (FuncDef) decl;
FuncInfo methodInfo = analyzeFunction(className, funcDef, 0, globalSymbols, null);
this.functions.add(methodInfo);
classInfo.addMethod(methodInfo);
}
}
return classInfo;
}
/**
* Analyze a function or method definition FUNCDEF at nesting depth DEPTH and return the
* resulting Info object. Analyze any nested functions recursively. The FuncInfo's symbol table
* is completely populated by analyzing all the params, local vars, global and nonlocal var
* declarations.
*
* <p>CONTAINER is the fully qualified name of the containing function/class, or null for global
* functions. PARENTSYMBOLTABLE symbol table contains symbols inherited from outer regions (that
* of the containing function/method for nested function definitions, and the global symbol
* table for global function / method definitions). PARENTFUNCINFO is the Info object for the
* parent function/method if this definition is nested, and otherwise null.
*/
protected FuncInfo analyzeFunction(
String container,
FuncDef funcDef,
int depth,
SymbolTable<SymbolInfo> parentSymbolTable,
FuncInfo parentFuncInfo) {
/* We proceed in three steps.
* 1. Create the FuncInfo object to be returned.
* 2. Populate it by analyzing all the parameters and local var
* definitions.
* 3. Now that the function's symbol table is built up, analyze
* nested function definitions.
* 4. Add the body to the function descriptor for code gen.
*/
String funcBaseName = funcDef.name.name;
String funcQualifiedName =
container != null ? String.format("%s.%s", container, funcBaseName) : funcBaseName;
FuncInfo funcInfo =
makeFuncInfo(
funcQualifiedName,
depth,
ValueType.annotationToValueType(funcDef.returnType),
parentSymbolTable,
parentFuncInfo,
this::emitUserDefinedFunction);
for (TypedVar param : funcDef.params) {
ValueType paramType = ValueType.annotationToValueType(param.type);
StackVarInfo paramInfo =
makeStackVarInfo(param.identifier.name, paramType, null, funcInfo);
funcInfo.addParam(paramInfo);
}
LocalDeclAnalyzer localDefs = new LocalDeclAnalyzer(funcInfo);
for (Declaration decl : funcDef.declarations) {
decl.dispatch(localDefs);
}
NestedFuncAnalyzer nestedFuncs = new NestedFuncAnalyzer(funcInfo);
for (Declaration decl : funcDef.declarations) {
decl.dispatch(nestedFuncs);
}
funcInfo.addBody(funcDef.statements);
return funcInfo;
}
/** Analyzer for local variable declarations in a function. */
protected class LocalDeclAnalyzer extends AbstractNodeAnalyzer<Void> {
/** The descriptor for the function being analyzed. */
private final FuncInfo funcInfo;
/** A new analyzer for a function with descriptor FUNCINFO0. */
protected LocalDeclAnalyzer(FuncInfo funcInfo0) {
funcInfo = funcInfo0;
}
@Override
public Void analyze(VarDef localVarDef) {
ValueType localVarType = ValueType.annotationToValueType(localVarDef.var.type);
StackVarInfo localVar =
makeStackVarInfo(
localVarDef.var.identifier.name,
localVarType,
localVarDef.value,
funcInfo);
funcInfo.addLocal(localVar);
return null;
}
@Override
public Void analyze(GlobalDecl decl) {
SymbolInfo symInfo = globalSymbols.get(decl.getIdentifier().name);
assert symInfo instanceof GlobalVarInfo
: "Semantic analysis should ensure that global var exists";
GlobalVarInfo globalVar = (GlobalVarInfo) symInfo;
funcInfo.getSymbolTable().put(globalVar.getVarName(), globalVar);
return null;
}
@Override
public Void analyze(NonLocalDecl decl) {
assert funcInfo.getSymbolTable().get(decl.getIdentifier().name) instanceof StackVarInfo
: "Semantic analysis should ensure nonlocal var exists";
return null;
}
}
/** Analyzer for nested function declarations in a function. */
protected class NestedFuncAnalyzer extends AbstractNodeAnalyzer<Void> {
/** Descriptor for the function being analyzed. */
private final FuncInfo funcInfo;
/** A new analyzer for a function with descriptor FUNCINFO0. */
protected NestedFuncAnalyzer(FuncInfo funcInfo0) {
funcInfo = funcInfo0;
}
@Override
public Void analyze(FuncDef nestedFuncDef) {
FuncInfo nestedFuncInfo =
analyzeFunction(
funcInfo.getFuncName(),
nestedFuncDef,
funcInfo.getDepth() + 1,
funcInfo.getSymbolTable(),
funcInfo);
functions.add(nestedFuncInfo);
funcInfo.getSymbolTable().put(nestedFuncInfo.getBaseName(), nestedFuncInfo);
return null;
}
}
/*------------------------------------------------------------*
* *
* EMITING DATA SECTION FOR GLOBALS+PROTOTYPES+CONSTANTS *
* (Students can ignore these methods as all the work has *
* been done and does not need to be modified/extended) *
* *
*------------------------------------------------------------*/
/** Emit code to align next data item to word boundary. */
protected void alignObject() {
int wordSizeLog2 = 31 - Integer.numberOfLeadingZeros(wordSize);
backend.alignNext(wordSizeLog2);
}
/** Emit the constant section containing the prototype FOR the class defined by CLASSINFO. */
protected void emitPrototype(ClassInfo classInfo) {
backend.emitGlobalLabel(classInfo.getPrototypeLabel());
backend.emitWordLiteral(
classInfo.getTypeTag(),
String.format("Type tag for class: %s", classInfo.getClassName()));
backend.emitWordLiteral(classInfo.attributes.size() + HEADER_SIZE, "Object size");
backend.emitWordAddress(classInfo.getDispatchTableLabel(), "Pointer to dispatch table");
for (VarInfo attr : classInfo.attributes) {
String cmnt = String.format("Initial value of attribute: %s", attr.getVarName());
emitConstant(attr.getInitialValue(), attr.getVarType(), cmnt);
}
alignObject();
}
/** Emit a word containing a constant int representing VALUE */
protected void emitConstantInt(int value, String comment) {
backend.emitWordLiteral(value, comment);
}
/** Emit a word containing a constant bool representing VALUE */
protected void emitConstantBool(boolean value, String comment) {
backend.emitWordLiteral(value ? 1 : 0, comment);
}
/**
* Emit a word containing a constant representing VALUE, assuming that it will be interpreted as
* a value of static type TYPE. VALUE may be null, indicating None. TYPE may be null, indicating
* object. COMMENT is an optional comment.
*/
protected void emitConstant(Literal value, ValueType type, String comment) {
if (type != null && type.equals(Type.INT_TYPE)) {
this.emitConstantInt(((IntegerLiteral) value).value, comment);
} else if (type != null && type.equals(Type.BOOL_TYPE)) {
this.emitConstantBool(((BooleanLiteral) value).value, comment);
} else {
backend.emitWordAddress(constants.fromLiteral(value), comment);
}
}
/** Emit code for all constants. */
protected void emitConstants() {
backend.emitGlobalLabel(constants.falseConstant);
backend.emitWordLiteral(boolClass.getTypeTag(), "Type tag for class: bool");
backend.emitWordLiteral(boolClass.attributes.size() + HEADER_SIZE, "Object size");
backend.emitWordAddress(boolClass.getDispatchTableLabel(), "Pointer to dispatch table");
backend.emitWordLiteral(0, "Constant value of attribute: __bool__");
alignObject();
backend.emitGlobalLabel(constants.trueConstant);
backend.emitWordLiteral(boolClass.getTypeTag(), "Type tag for class: bool");
backend.emitWordLiteral(boolClass.attributes.size() + HEADER_SIZE, "Object size");
backend.emitWordAddress(boolClass.getDispatchTableLabel(), "Pointer to dispatch table");
backend.emitWordLiteral(1, "Constant value of attribute: __bool__");
alignObject();
for (Map.Entry<String, Label> e : constants.strConstants.entrySet()) {
String value = e.getKey();
Label label = e.getValue();
int numWordsForCharacters = value.length() / wordSize + 1;
backend.emitGlobalLabel(label);
backend.emitWordLiteral(strClass.getTypeTag(), "Type tag for class: str");
backend.emitWordLiteral(3 + 1 + numWordsForCharacters, "Object size");
backend.emitWordAddress(strClass.getDispatchTableLabel(), "Pointer to dispatch table");
this.emitConstantInt(value.length(), "Constant value of attribute: __len__");
backend.emitString(value, "Constant value of attribute: __str__");
alignObject();
}
for (Map.Entry<Integer, Label> e : constants.intConstants.entrySet()) {
Integer value = e.getKey();
Label label = e.getValue();
backend.emitGlobalLabel(label);
backend.emitWordLiteral(intClass.getTypeTag(), "Type tag for class: int");
backend.emitWordLiteral(intClass.attributes.size() + HEADER_SIZE, "Object size");
backend.emitWordAddress(intClass.getDispatchTableLabel(), "Pointer to dispatch table");
backend.emitWordLiteral(value, "Constant value of attribute: __int__");
alignObject();
}
}
/** Emit the method dispatching table for CLASSINFO. */
protected void emitDispatchTable(ClassInfo classInfo) {
Label dispatchTableLabel = classInfo.getDispatchTableLabel();
if (dispatchTableLabel == null) {
return;
}
backend.emitGlobalLabel(dispatchTableLabel);
for (FuncInfo method : classInfo.methods) {
String cmnt =
String.format(
"Implementation for method: %s.%s",
classInfo.getClassName(), method.getBaseName());
backend.emitWordAddress(method.getCodeLabel(), cmnt);
}
}
/*------------------------------------------------------------*
* *
* UTILITY METHODS TO GET BYTE OFFSETS IN OBJECT LAYOUT *
* (Students will find these methods helpful to use in *
* their sub-class when generating code for expressions) *
* *
*------------------------------------------------------------*/
/** Return offset of the type-tag field in an object. */
protected int getTypeTagOffset() {
return 0 * wordSize;
}
/** Return offset of the size field in an object. */
protected int getObjectSizeOffset() {
return 1 * wordSize;
}
/** Return offset of the start of the pointer to the method-dispatching table in an object. */
protected int getDispatchTableOffset() {
return 2 * wordSize;
}
/** Return the offset of the ATTRNAME attribute of an object of type described by CLASSINFO. */
protected int getAttrOffset(ClassInfo classInfo, String attrName) {
int attrIndex = classInfo.getAttributeIndex(attrName);
assert attrIndex >= 0 : "Type checker ensures that attributes are valid";
return wordSize * (HEADER_SIZE + attrIndex);
}
/**
* Return the offset of the method named METHODNAME in the method-dispatching table for the
* class described by CLASSINFO.
*/
protected int getMethodOffset(ClassInfo classInfo, String methodName) {
int methodIndex = classInfo.getMethodIndex(methodName);
assert methodIndex >= 0 : "Type checker ensures that attributes are valid";
return wordSize * methodIndex;
}
/*------------------------------------------------------------*
* *
* UNIMPLEMENTED METHODS (should be extended) *
* *
*------------------------------------------------------------*/
/** Emits code for STATEMENTS, assumed to be at the top level. */
protected abstract void emitTopLevel(List<Stmt> statements);
/** Emits code for the body of user-defined function FUNCINFO. */
protected abstract void emitUserDefinedFunction(FuncInfo funcInfo);
/**
* Emits code outside the ChocoPy program.
*
* <p>Custom assembly routines (that may be jumpable from program statements) can be emitted
* here.
*/
protected abstract void emitCustomCode();
/*------------------------------------------------------------*
* *
* PREDEFINED FUNCTIONS AND ROUTINES *
* (Students may find a cursory read of these methods to *
* be useful to get an idea for how code can be emitted) *
* *
*------------------------------------------------------------*/
/**
* Return Risc V assembler code for function NAME from directory LIB, or null if it does not
* exist. LIB must end in '/'.
*/
protected String getStandardLibraryCode(String name, String lib) {
String simpleName = name.replace("$", "") + ".s";
return getResourceFileAsString(lib + simpleName);
}
/**
* Emit label and body for the function LABEL, taking the source from directory LIB (must end in
* '/').
*/
protected void emitStdFunc(Label label, String lib) {
emitStdFunc(label, label.toString(), lib);
}
/**
* Emit label and body for the function LABEL, taking the source from SOURCEFILE.s in directory
* LIB (must end in '/').
*/
protected void emitStdFunc(Label label, String sourceFile, String lib) {
String source = getStandardLibraryCode(sourceFile, lib);
if (source == null) {
throw fatal("Code for %s is missing.", sourceFile);
}
backend.emitGlobalLabel(label);
backend.emit(convertLiterals(source));
}
/**
* Emit label and body for the function LABEL, taking the source from from the default library
* directory.
*/
protected void emitStdFunc(Label label) {
emitStdFunc(label, LIBRARY_CODE_DIR);
}
/**
* Emit label and body for the function NAME, taking the source from from the default library
* directory.
*/
protected void emitStdFunc(String name) {
emitStdFunc(new Label(name));
}
/**
* Emit label and body for the function described by FUNCINFO, taking the source from from the
* default library directory.
*/
protected void emitStdFunc(FuncInfo funcInfo) {
emitStdFunc(funcInfo.getCodeLabel());
}
/** Pattern matching STRING["..."]. */
private static final Pattern STRING_LITERAL_PATN = Pattern.compile("STRING\\[\"(.*?)\"\\]");
/**
* Return result of converting STRING["..."] notations in SOURCE to labels of string constants,
* adding those constants to the pool.
*/
private String convertLiterals(String source) {
Matcher matcher = STRING_LITERAL_PATN.matcher(source);
StringBuffer result = new StringBuffer();
while (matcher.find()) {
String r = constants.getStrConstant(matcher.group(1)).toString();
matcher.appendReplacement(
result, pad(r, ' ', matcher.end(0) - matcher.start(0), false));
}
return matcher.appendTail(result).toString();
}
}
@@ -0,0 +1,116 @@
package chocopy.common.codegen;
import chocopy.common.astnodes.*;
import java.util.HashMap;
import java.util.Map;
/**
* A store for caching and re-using program constants that are represented as immutable objects.
*
* <p>Constants are emitted in assembly in the DATA section, and therefore are represented by their
* labels.
*/
public class Constants {
/** A counter used to generate unique label names for constants. */
protected int nextLabelSuffix = 0;
/** The constant representing the boolean `False`. */
final Label falseConstant = generateConstantLabel();
/**
* The constant representing the boolean `True`. This immediately follows falseConstant in
* static memory.
*/
final Label trueConstant = generateConstantLabel();
/** A cache for integer-valued constants. */
final Map<Integer, Label> intConstants = new HashMap<>();
/** A cache for string-valued constants. */
final Map<String, Label> strConstants = new HashMap<>();
/**
* Returns the next unique label suffix for constants.
*
* @return the next unique label suffix for constants
*/
protected int getNextLabelSuffix() {
return nextLabelSuffix++;
}
/**
* Generates a fresh label for constants.
*
* <p>This label is guaranteed to be unique amongst labels generated by invoking this method.
* All such labels have a prefix of `const_`.
*
* @return a fresh label
*/
public Label generateConstantLabel() {
return new Label(String.format("const_%d", getNextLabelSuffix()));
}
/**
* Returns the label for a `bool` constant.
*
* @param value the boolean value
* @return the label for the boolean value
*/
public Label getBoolConstant(boolean value) {
return value ? trueConstant : falseConstant;
}
/**
* Returns the label for am `int` constant.
*
* @param value the integer value
* @return the label for the integer value
*/
public Label getIntConstant(int value) {
if (intConstants.containsKey(value)) {
return intConstants.get(value);
} else {
Label newLabel = generateConstantLabel();
intConstants.put(value, newLabel);
return newLabel;
}
}
/**
* Returns the label for a `str` constant.
*
* @param value the string value
* @return the label for the string value
*/
public Label getStrConstant(String value) {
if (strConstants.containsKey(value)) {
return strConstants.get(value);
} else {
Label newLabel = generateConstantLabel();
strConstants.put(value, newLabel);
return newLabel;
}
}
/**
* Converts a constant literal in the AST to a constant for code generation.
*
* @param literal the literal expression in the AST
* @return a {@link Label} representing a constant int/str/bool, or `null` representing the None
* literal
*/
public Label fromLiteral(Literal literal) {
if (literal instanceof IntegerLiteral) {
return getIntConstant(((IntegerLiteral) literal).value);
} else if (literal instanceof StringLiteral) {
return getStrConstant(((StringLiteral) literal).value);
} else if (literal instanceof BooleanLiteral) {
return getBoolConstant(((BooleanLiteral) literal).value);
} else {
assert literal == null || literal instanceof NoneLiteral;
return null;
}
}
}
@@ -0,0 +1,206 @@
package chocopy.common.codegen;
import chocopy.common.analysis.SymbolTable;
import chocopy.common.analysis.types.ValueType;
import chocopy.common.astnodes.Stmt;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
/**
* A descriptor for function and method definitions.
*
* <p>This class stores information required for code generation such as the information about a
* function's parameters, local variables, the local symbol table, the function body, and the label
* where the code for the body is generated.
*/
public class FuncInfo extends SymbolInfo {
/**
* The fully-qualified name of the function.
*
* <p>All functions in a ChocoPy program have a unique fully-qualified name. Global functions
* defined with name `f` have fully-qualified name `f`. Methods `m` in a class `C` have
* fully-qualified name `C.m`. Functions `f` nested inside another function with fully-qualified
* name `F` have a fully-qualified name of `F.f`.
*/
protected final String funcName;
/**
* The static depth of a function.
*
* <p>Global functions and class methods have a static depth of 0. Nested functions that are
* defined in the body of a function with static depth `D` have a static depth of `D+1`.
*/
protected final int depth;
/** This function's return type. */
protected final ValueType returnType;
/** A list of parameter names. */
protected final List<String> params = new ArrayList<>();
/** A list of local variable descriptors. */
protected final List<StackVarInfo> locals = new ArrayList<>();
/** The function body. */
protected final List<Stmt> statements = new ArrayList<>();
/** The local symbol table that binds identifiers seen in the function's body. */
protected final SymbolTable<SymbolInfo> symbolTable;
/** The label of the generated code for the function's body. */
protected final Label codeLabel;
/** The descriptor of the enclosing function (this is only non-null for nested functions). */
protected final FuncInfo parentFuncInfo;
/**
* A method that is invoked to emit the function's body.
*
* <p>The method should accept one parameter of type `FuncInfo`.
*/
protected final Consumer<FuncInfo> emitter;
/**
* Creates a descriptor for a function or method with fully qualified name FUNCNAME returning
* type RETURNTYPE that is at nesting depth DEPTH. The code label is formed from FUNCNAME by
* prepending a $ sign to prevent collisions. PARENTSYMBOLTABLE is the symbol table of the
* containing region. PARENTFUNCINFO is the descriptor of the enclosing function (null for
* global functions and methods). EMITTER encapsulates a method that emits the function's body
* (this is usually a generic emitter for user-defined functions/methods, and a special emitter
* for pre-defined functions/methods).
*/
public FuncInfo(
String funcName,
int depth,
ValueType returnType,
SymbolTable<SymbolInfo> parentSymbolTable,
FuncInfo parentFuncInfo,
Consumer<FuncInfo> emitter) {
this.funcName = funcName;
this.codeLabel = new Label(String.format("$%s", funcName));
this.depth = depth;
this.returnType = returnType;
this.symbolTable = new SymbolTable<>(parentSymbolTable);
this.parentFuncInfo = parentFuncInfo;
this.emitter = emitter;
}
/** Adds parameter with descriptor PARAMINFO to this function. */
public void addParam(StackVarInfo paramInfo) {
this.params.add(paramInfo.getVarName());
this.symbolTable.put(paramInfo.getVarName(), paramInfo);
}
/** Adds a local variable with descriptor STACKVARINFO to this function. */
public void addLocal(StackVarInfo stackVarInfo) {
this.locals.add(stackVarInfo);
this.symbolTable.put(stackVarInfo.getVarName(), stackVarInfo);
}
/** Adds STMTS to the function's body. */
public void addBody(List<Stmt> stmts) {
statements.addAll(stmts);
}
/**
* Returns the index of parameter or local variable NAME in the function's activation record.
*
* <p>The convention is that for a function with N params and K local vars, the i`th param is at
* index `i` and the j`th local var is at index `N+j+2`. In all, a function stores N+K+2
* variables contiguously in its activation record, where the N+1st is the frame pointer and the
* N+2nd is the return address.
*
* <p>Caution: this is an index (starting at 0), and not an offset in number of bytes.
*/
public int getVarIndex(String name) {
int idx = params.indexOf(name);
if (idx >= 0) {
return idx;
}
for (int i = 0; i < locals.size(); i++) {
if (locals.get(i).getVarName().equals(name)) {
return i + params.size() + 2;
}
}
String msg = String.format("%s is not a var defined in function %s", name, funcName);
throw new IllegalArgumentException(msg);
}
/** Returns the label corresponding to the function's body in assembly. */
public Label getCodeLabel() {
return codeLabel;
}
/**
* Returns the function's defined name in the program. This is the last component of the
* dot-separated fully-qualified name.
*/
public String getBaseName() {
int rightmostDotIndex = funcName.lastIndexOf('.');
if (rightmostDotIndex == -1) {
return funcName;
} else {
return funcName.substring(rightmostDotIndex + 1);
}
}
/** Returns the function's fully-qualified name. */
public String getFuncName() {
return funcName;
}
/** Returns the function's static nesting depth. */
public int getDepth() {
return depth;
}
/** Returns the function's parameters in order of definition. */
public List<String> getParams() {
return params;
}
/** Returns the return type of this function. */
public ValueType getReturnType() {
return returnType;
}
/**
* Returns the function's explicitly defined local variables, excluding parameters.
*
* <p>This list is mainly used in generating code for initializing local variables that are not
* parameters.
*/
public List<StackVarInfo> getLocals() {
return locals;
}
/** Returns the list of statements in the function's body. */
public List<Stmt> getStatements() {
return statements;
}
/**
* Returns the function's local symbol table.
*
* @return the function's local symbol table
*/
public SymbolTable<SymbolInfo> getSymbolTable() {
return symbolTable;
}
/**
* Returns the parent function's descriptor for nested functions, and null if this function is
* not nested.
*/
public FuncInfo getParentFuncInfo() {
return parentFuncInfo;
}
/** Emits the function's body. */
public void emitBody() {
emitter.accept(this);
}
}
@@ -0,0 +1,28 @@
package chocopy.common.codegen;
import chocopy.common.analysis.types.ValueType;
import chocopy.common.astnodes.Literal;
/** Code-generation related information about a global variable. */
public class GlobalVarInfo extends VarInfo {
/**
* This variable resides in static storage tagged with LABEL. The label is prepended with "$" to
* prevent name clashes.
*/
protected final Label label;
/**
* A descriptor for a global variable named VARNAME of type VARTYPE whose initial value is
* labeled with INITIALVALUE (null if no initialization value).
*/
public GlobalVarInfo(String varName, ValueType varType, Literal initialValue) {
super(varName, varType, initialValue);
this.label = new Label(String.format("$%s", varName));
}
/** Return the code location of this variable. */
public Label getLabel() {
return label;
}
}
@@ -0,0 +1,40 @@
package chocopy.common.codegen;
import java.util.Objects;
/** A label in assembly. */
public class Label {
/** The name of the label. */
public final String labelName;
/** A new label with name LABELNAME. */
public Label(String labelName) {
this.labelName = labelName;
}
/** {@inheritDoc} */
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Label label = (Label) o;
return Objects.equals(labelName, label.labelName);
}
/** {@inheritDoc} */
@Override
public int hashCode() {
return Objects.hash(labelName);
}
/** {@inheritDoc} */
@Override
public String toString() {
return labelName;
}
}
@@ -0,0 +1,606 @@
package chocopy.common.codegen;
import java.io.PrintWriter;
import java.io.StringWriter;
/** RISC V assembly-language generation utilities. */
public class RiscVBackend {
/** Accumulator for assembly code output. */
protected final StringWriter asmText = new StringWriter();
/** Allows print, println, and printf of assembly code. */
private final PrintWriter out = new PrintWriter(asmText);
/** The word size in bytes for RISC-V 32-bit. */
protected static final int WORD_SIZE = 4;
/** The RISC-V registers. */
public enum Register {
A0("a0"),
A1("a1"),
A2("a2"),
A3("a3"),
A4("a4"),
A5("a5"),
A6("a6"),
A7("a7"),
T0("t0"),
T1("t1"),
T2("t2"),
T3("t3"),
T4("t4"),
T5("t5"),
T6("t6"),
S1("s1"),
S2("s2"),
S3("s3"),
S4("s4"),
S5("s5"),
S6("s6"),
S7("s7"),
S8("s8"),
S9("s9"),
S10("s10"),
S11("s11"),
FP("fp"),
SP("sp"),
GP("gp"),
RA("ra"),
ZERO("zero");
/** The name of the register used in assembly. */
protected final String name;
/** This register's code representation is NAME. */
Register(String name) {
this.name = name;
}
@Override
public String toString() {
return this.name;
}
}
@Override
public String toString() {
return asmText.toString();
}
/**
* Define @NAME to have the value VALUE. Here, NAME is assumed to be an identifier consisting of
* letters, digits, underscores, and any of the characters '$' or '.', and that does not start
* with a digit. Value may be a numeral or another symbol.
*/
public void defineSym(String name, String value) {
if (name.startsWith("@")) {
emitInsn(String.format(".equiv %s, %s", name, value), null);
} else {
emitInsn(String.format(".equiv @%s, %s", name, value), null);
}
}
/**
* Define @NAME to have the value VALUE, where value is converted to a string. See {@link
* #defineSym(java.lang.String, java.lang.String)}.
*/
public void defineSym(String name, int value) {
defineSym(name, Integer.toString(value));
}
/**
* Returns the word size in bytes.
*
* <p>This method is used instead of directly accessing the static field {@link #WORD_SIZE}, so
* that this class may be extended with alternate word sizes.
*/
public int getWordSize() {
return WORD_SIZE;
}
/** Emit the text STR to the output stream verbatim. STR should have no trailing newline. */
protected void emit(String str) {
out.println(str);
}
/** Emit instruction or directive INSN along with COMMENT as a one-line comment, if non-null. */
public void emitInsn(String insn, String comment) {
if (comment != null) {
emit(String.format(" %-40s # %s", insn, comment));
} else {
emitInsn(insn);
}
}
/** Emit instruction or directive INSN without a comment. */
protected void emitInsn(String insn) {
emit(String.format(" %s", insn));
}
/**
* Emit a local label marker for LABEL with one-line comment COMMENT (null if missing). Invoke
* only once per unique label.
*/
public void emitLocalLabel(Label label, String comment) {
if (comment != null) {
emit(String.format("%-42s # %s", label + ":", comment));
} else {
emit(String.format("%s:", label + ":"));
}
}
/** Emit a global label marker for LABEL. Invoke only once per unique label. */
public void emitGlobalLabel(Label label) {
emit(String.format("\n.globl %s", label));
emit(String.format("%s:", label));
}
/**
* Emit a data word containing VALUE as an integer value. COMMENT is a emitted as a one-line
* comment, if non-null.
*/
public void emitWordLiteral(Integer value, String comment) {
emitInsn(String.format(".word %s", value), comment);
}
/**
* Emit a data word containing the address ADDR, or 0 if LABEL is null. COMMENT is a emitted as
* a one-line comment, if non-null.
*/
public void emitWordAddress(Label addr, String comment) {
if (addr == null) {
emitWordLiteral(0, comment);
} else {
emitInsn(String.format(".word %s", addr), comment);
}
}
/**
* Emit VALUE as an ASCII null-terminated string constant, with COMMENT as its one-line comment,
* if non-null.
*/
public void emitString(String value, String comment) {
String quoted =
value.replace("\\", "\\\\")
.replace("\n", "\\n")
.replace("\t", "\\t")
.replace("\"", "\\\"");
emitInsn(String.format(".string \"%s\"", quoted), comment);
}
/** Mark the start of a data section. */
public void startData() {
emit("\n.data");
}
/** Mark the start of a code/text section. */
public void startCode() {
emit("\n.text");
}
/** Align the next instruction/word in memory to a multiple of 2**POW bytes. */
public void alignNext(int pow) {
emitInsn(String.format(".align %d", pow));
}
/** Emit an ecall instruction, with one-line comment COMMENT, if non-null. */
public void emitEcall(String comment) {
emitInsn("ecall", comment);
}
/**
* Emit a load-address instruction with destination RD and source LABEL. COMMENT is an optional
* one-line comment (null if missing).
*/
public void emitLA(Register rd, Label label, String comment) {
emitInsn(String.format("la %s, %s", rd, label), comment);
}
/**
* Emit a load-immediate pseudo-op to set RD to IMM. COMMENT is an optional one-line comment
* (null if missing).
*/
public void emitLI(Register rd, Integer imm, String comment) {
emitInsn(String.format("li %s, %d", rd, imm), comment);
}
/**
* Emit a load-upper-immediate instruction to set the upper 20 bits of RD to IMM, where 0 <= IMM
* < 2**20. COMMENT is an optional one-line comment (null if missing).
*/
public void emitLUI(Register rd, Integer imm, String comment) {
emitInsn(String.format("lui %s, %d", rd, imm), comment);
}
/**
* Emit a move instruction to set RD to the contents of RS. COMMENT is an optional one-line
* comment (null if missing).
*/
public void emitMV(Register rd, Register rs, String comment) {
emitInsn(String.format("mv %s, %s", rd, rs), comment);
}
/**
* Emit a jump-register (computed jump) instruction to the address in RS. COMMENT is an optional
* one-line comment (null if missing).
*/
public void emitJR(Register rs, String comment) {
emitInsn(String.format("jr %s", rs), comment);
}
/**
* Emit a jump (unconditional jump) instruction to LABEL. COMMENT is an optional one-line
* comment (null if missing).
*/
public void emitJ(Label label, String comment) {
emitInsn(String.format("j %s", label), comment);
}
/**
* Emit a jump-and-link instruction to LABEL. COMMENT is an optional one-line comment (null if
* missing).
*/
public void emitJAL(Label label, String comment) {
emitInsn(String.format("jal %s", label), comment);
}
/**
* Emit a computed-jump-and-link instruction to the address in RS. COMMENT is an optional
* one-line comment (null if missing).
*/
public void emitJALR(Register rs, String comment) {
emitInsn(String.format("jalr %s", rs), comment);
}
/**
* Emit an add-immediate instruction performing RD = RS + IMM. Requires -2048 <= IMM < 2048.
* COMMENT is an optional one-line comment (null if missing).
*/
public void emitADDI(Register rd, Register rs, Integer imm, String comment) {
emitInsn(String.format("addi %s, %s, %d", rd, rs, imm), comment);
}
/**
* Emit an add-immediate instruction performing RD = RS + IMM. Here, IMM is a string generally
* containing a symbolic assembler constant (see defineSym) representing an integer value, or an
* expression of the form @NAME+NUM or @NAME-NUM. COMMENT is an optional one-line comment (null
* if missing).
*/
public void emitADDI(Register rd, Register rs, String imm, String comment) {
emitInsn(String.format("addi %s, %s, %s", rd, rs, imm), comment);
}
/**
* Emit an add instruction performing RD = RS1 + RS2 mod 2**32. COMMENT is an optional one-line
* comment (null if missing).
*/
public void emitADD(Register rd, Register rs1, Register rs2, String comment) {
emitInsn(String.format("add %s, %s, %s", rd, rs1, rs2), comment);
}
/**
* Emit a subtract instruction performing RD = RS1 - RS2 mod 2**32. COMMENT is an optional
* one-line comment (null if missing).
*/
public void emitSUB(Register rd, Register rs1, Register rs2, String comment) {
emitInsn(String.format("sub %s, %s, %s", rd, rs1, rs2), comment);
}
/**
* Emit a multiply instruction performing RD = RS1 * RS2 mod 2**32. COMMENT is an optional
* one-line comment (null if missing).
*/
public void emitMUL(Register rd, Register rs1, Register rs2, String comment) {
emitInsn(String.format("mul %s, %s, %s", rd, rs1, rs2), comment);
}
/**
* Emit a signed integer divide instruction performing RD = RS1 / RS2 mod 2**32, rounding the
* result toward 0. If RS2 == 0, sets RD to -1. If RS1 == -2**31 and RS2 == -1, sets RD to
* -2**31. COMMENT is an optional one-line comment (null if missing).
*/
public void emitDIV(Register rd, Register rs1, Register rs2, String comment) {
emitInsn(String.format("div %s, %s, %s", rd, rs1, rs2), comment);
}
/**
* Emit a remainder instruction: RD = RS1 rem RS2 defined so that (RS1 / RS2) * RS2 + (RS1 rem
* RS2) == RS1, where / is as for emitDIV. COMMENT is an optional one-line comment (null if
* missing).
*/
public void emitREM(Register rd, Register rs1, Register rs2, String comment) {
emitInsn(String.format("rem %s, %s, %s", rd, rs1, rs2), comment);
}
/**
* Emit an xor instruction: RD = RS1 ^ RS2. COMMENT is an optional one-line comment (null if
* missing).
*/
public void emitXOR(Register rd, Register rs1, Register rs2, String comment) {
emitInsn(String.format("xor %s, %s, %s", rd, rs1, rs2), comment);
}
/**
* Emit an xor-immediate instruction: RD = RS ^ IMM, where -2048 <= IMM < 2048. COMMENT is an
* optional one-line comment (null if missing).
*/
public void emitXORI(Register rd, Register rs, Integer imm, String comment) {
emitInsn(String.format("xori %s, %s, %d", rd, rs, imm), comment);
}
/**
* Emit a bitwise and instruction: RD = RS1 & RS2. COMMENT is an optional one-line comment (null
* if missing).
*/
public void emitAND(Register rd, Register rs1, Register rs2, String comment) {
emitInsn(String.format("and %s, %s, %s", rd, rs1, rs2), comment);
}
/**
* Emit a bitwise and-immediate instruction: RD = RS & IMM, where -2048 <= IMM < 2048. COMMENT
* is an optional one-line comment (null if missing).
*/
public void emitANDI(Register rd, Register rs, Integer imm, String comment) {
emitInsn(String.format("andi %s, %s, %d", rd, rs, imm), comment);
}
/**
* Emit a bitwise or instruction: RD = RS1 | RS2. COMMENT is an optional one-line comment (null
* if missing).
*/
public void emitOR(Register rd, Register rs1, Register rs2, String comment) {
emitInsn(String.format("or %s, %s, %s", rd, rs1, rs2), comment);
}
/**
* Emit a bitwise or-immediate instruction: RD = RS | IMM, where -2048 <= IMM < 2048. COMMENT is
* an optional one-line comment (null if missing).
*/
public void emitORI(Register rd, Register rs, Integer imm, String comment) {
emitInsn(String.format("ori %s, %s, %d", rd, rs, imm), comment);
}
/**
* Emit a logical left shift instruction: RD = RS1 << (RS2 & 0x31). COMMENT is an optional
* one-line comment (null if missing).
*/
public void emitSLL(Register rd, Register rs1, Register rs2, String comment) {
emitInsn(String.format("sll %s, %s, %s", rd, rs1, rs2), comment);
}
/**
* Emit a logical left shift instruction: RD = RS << (IMM & 0x31). COMMENT is an optional
* one-line comment (null if missing).
*/
public void emitSLLI(Register rd, Register rs, Integer imm, String comment) {
emitInsn(String.format("slli %s, %s, %d", rd, rs, imm), comment);
}
/**
* Emit a logical right shift instruction: RD = RS1 >>> (RS2 & 0x31). COMMENT is an optional
* one-line comment (null if missing).
*/
public void emitSRL(Register rd, Register rs1, Register rs2, String comment) {
emitInsn(String.format("srl %s, %s, %s", rd, rs1, rs2), comment);
}
/**
* Emit a logical right shift instruction: RD = RS >>> (IMM & 0x31). COMMENT is an optional
* one-line comment (null if missing).
*/
public void emitSRLI(Register rd, Register rs, Integer imm, String comment) {
emitInsn(String.format("srli %s, %s, %d", rd, rs, imm), comment);
}
/**
* Emit an arithmetic right shift instruction: RD = RS1 >> (RS2 & 0x31). COMMENT is an optional
* one-line comment (null if missing).
*/
public void emitSRA(Register rd, Register rs1, Register rs2, String comment) {
emitInsn(String.format("sra %s, %s, %s", rd, rs1, rs2), comment);
}
/**
* Emit an arithmetic right shift instruction: RD = RS >> (IMM & 0x31). COMMENT is an optional
* one-line comment (null if missing).
*/
public void emitSRAI(Register rd, Register rs, Integer imm, String comment) {
emitInsn(String.format("srai %s, %s, %d", rd, rs, imm), comment);
}
/**
* Emit a load-word instruction: RD = MEMORY[RS + IMM]:4, where -2048 <= IMM < 2048. COMMENT is
* an optional one-line comment (null if missing).
*/
public void emitLW(Register rd, Register rs, Integer imm, String comment) {
emitInsn(String.format("lw %s, %d(%s)", rd, imm, rs), comment);
}
/**
* Emit a load-word instruction: RD = MEMORY[RS + IMM]:4, where -2048 <= IMM < 2048. Here, IMM
* is symbolic constant expression (see emitADDI). COMMENT is an optional one-line comment (null
* if missing).
*/
public void emitLW(Register rd, Register rs, String imm, String comment) {
emitInsn(String.format("lw %s, %s(%s)", rd, imm, rs), comment);
}
/**
* Emit a store-word instruction: MEMORY[RS1 + IMM]:4 = RS2, where -2048 <= IMM < 2048. COMMENT
* is an optional one-line comment (null if missing).
*/
public void emitSW(Register rs2, Register rs1, Integer imm, String comment) {
emitInsn(String.format("sw %s, %d(%s)", rs2, imm, rs1), comment);
}
/**
* Emit a store-word instruction: MEMORY[RS1 + IMM]:4 = RS2, where -2048 <= IMM < 2048. Here,
* IMM is symbolic constant expression (see emitADDI). COMMENT is an optional one-line comment
* (null if missing).
*/
public void emitSW(Register rs2, Register rs1, String imm, String comment) {
emitInsn(String.format("sw %s, %s(%s)", rs2, imm, rs1), comment);
}
/**
* Emit a load-word instruction for globals: RD = MEMORY[LABEL]:4. COMMENT is an optional
* one-line comment (null if missing).
*/
public void emitLW(Register rd, Label label, String comment) {
emitInsn(String.format("lw %s, %s", rd, label), comment);
}
/**
* Emit a store-word instruction for globals: MEMORY[LABEL]:4 = RS, using TMP as a temporary
* register. COMMENT is an optional one-line comment (null if missing).
*/
public void emitSW(Register rs, Label label, Register tmp, String comment) {
emitInsn(String.format("sw %s, %s, %s", rs, label, tmp), comment);
}
/**
* Emit a load-byte instruction: RD = MEMORY[RS + IMM]:1, where -2048 <= IMM < 2048. Sign
* extends the byte loaded. COMMENT is an optional one-line comment (null if missing).
*/
public void emitLB(Register rd, Register rs, Integer imm, String comment) {
emitInsn(String.format("lb %s, %d(%s)", rd, imm, rs), comment);
}
/**
* Emit a load-byte-unsigned instruction: RD = MEMORY[RS + IMM]:1, where -2048 <= IMM < 2048.
* Zero-extends the byte loaded. COMMENT is an optional one-line comment (null if missing).
*/
public void emitLBU(Register rd, Register rs, Integer imm, String comment) {
emitInsn(String.format("lbu %s, %d(%s)", rd, imm, rs), comment);
}
/**
* Emit a store-byte instruction: MEMORY[RS1 + IMM]:1 = RS2, where -2048 <= IMM < 2048. Assigns
* the low-order byte of RS2 to memory. COMMENT is an optional one-line comment (null if
* missing).
*/
public void emitSB(Register rs2, Register rs1, Integer imm, String comment) {
emitInsn(String.format("sb %s, %d(%s)", rs2, imm, rs1), comment);
}
/**
* Emit a branch-if-equal instruction: if RS1 == RS2 goto LABEL. COMMENT is an optional one-line
* comment (null if missing).
*/
public void emitBEQ(Register rs1, Register rs2, Label label, String comment) {
emitInsn(String.format("beq %s, %s, %s", rs1, rs2, label), comment);
}
/**
* Emit a branch-if-unequal instruction: if RS1 != RS2 goto LABEL. COMMENT is an optional
* one-line comment (null if missing).
*/
public void emitBNE(Register rs1, Register rs2, Label label, String comment) {
emitInsn(String.format("bne %s, %s, %s", rs1, rs2, label), comment);
}
/**
* Emit a branch-if-greater-or-equal (signed) instruction: if RS1 >= RS2 goto LABEL. COMMENT is
* an optional one-line comment (null if missing).
*/
public void emitBGE(Register rs1, Register rs2, Label label, String comment) {
emitInsn(String.format("bge %s, %s, %s", rs1, rs2, label), comment);
}
/**
* Emit a branch-if-greater-or-equal (unsigned) instruction: if RS1 >= RS2 goto LABEL. COMMENT
* is an optional one-line comment (null if missing).
*/
public void emitBGEU(Register rs1, Register rs2, Label label, String comment) {
emitInsn(String.format("bgeu %s, %s, %s", rs1, rs2, label), comment);
}
/**
* Emit a branch-if-less-than (signed) instruction: if RS1 < RS2 goto LABEL. COMMENT is an
* optional one-line comment (null if missing).
*/
public void emitBLT(Register rs1, Register rs2, Label label, String comment) {
emitInsn(String.format("blt %s, %s, %s", rs1, rs2, label), comment);
}
/**
* Emit a branch-if-less-than (unsigned) instruction: if RS1 < RS2 goto LABEL. COMMENT is an
* optional one-line comment (null if missing).
*/
public void emitBLTU(Register rs1, Register rs2, Label label, String comment) {
emitInsn(String.format("bltu %s, %s, %s", rs1, rs2, label), comment);
}
/**
* Emit a branch-if-zero instruction: if RS == 0 goto LABEL. COMMENT is an optional one-line
* comment (null if missing).
*/
public void emitBEQZ(Register rs, Label label, String comment) {
emitInsn(String.format("beqz %s, %s", rs, label), comment);
}
/**
* Emit a branch-if-not-zero instruction: if RS != 0 goto LABEL. COMMENT is an optional one-line
* comment (null if missing).
*/
public void emitBNEZ(Register rs, Label label, String comment) {
emitInsn(String.format("bnez %s, %s", rs, label), comment);
}
/**
* Emit a branch-if-less-than-zero instruction: if RS < 0 goto LABEL. COMMENT is an optional
* one-line comment (null if missing).
*/
public void emitBLTZ(Register rs, Label label, String comment) {
emitInsn(String.format("bltz %s, %s", rs, label), comment);
}
/**
* Emit a branch-if-greater-than-zero instruction: if RS > 0 goto LABEL. COMMENT is an optional
* one-line comment (null if missing).
*/
public void emitBGTZ(Register rs, Label label, String comment) {
emitInsn(String.format("bgtz %s, %s", rs, label), comment);
}
/**
* Emit a branch-if-less-than-equal-to-zero instruction: if RS <= 0 goto LABEL. COMMENT is an
* optional one-line comment (null if missing).
*/
public void emitBLEZ(Register rs, Label label, String comment) {
emitInsn(String.format("blez %s, %s", rs, label), comment);
}
/**
* Emit a branch-if-greater-than-equal-to-zero instruction: if RS >= 0 goto LABEL. COMMENT is an
* optional one-line comment (null if missing).
*/
public void emitBGEZ(Register rs, Label label, String comment) {
emitInsn(String.format("bgez %s, %s", rs, label), comment);
}
/**
* Emit a set-less-than instruction: RD = 1 if RS1 < RS2 else 0. COMMENT is an optional one-line
* comment (null if missing).
*/
public void emitSLT(Register rd, Register rs1, Register rs2, String comment) {
emitInsn(String.format("slt %s, %s, %s", rd, rs1, rs2), comment);
}
/**
* Emit a set-if-zero instruction: RD = 1 if RS == 0 else 0. COMMENT is an optional one-line
* comment (null if missing).
*/
public void emitSEQZ(Register rd, Register rs, String comment) {
emitInsn(String.format("seqz %s, %s", rd, rs), comment);
}
/**
* Emit a set-if-not-zero instruction: RD = 1 if RS != 0 else 0. COMMENT is an optional one-line
* comment (null if missing).
*/
public void emitSNEZ(Register rd, Register rs, String comment) {
emitInsn(String.format("snez %s, %s", rd, rs), comment);
}
}
@@ -0,0 +1,27 @@
package chocopy.common.codegen;
import chocopy.common.analysis.types.ValueType;
import chocopy.common.astnodes.Literal;
/** Code-generation information about a local variable or parameter. */
public class StackVarInfo extends VarInfo {
/** Information about the enclosing function. */
protected final FuncInfo funcInfo;
/**
* A descriptor for a local variable or parameter VARNAME of type VARTYPE, whose initial value
* is given by INITIALVALUE (null if no initial value), and which is nested immediately within
* the function described by FUNCINFO.
*/
public StackVarInfo(
String varName, ValueType varType, Literal initialValue, FuncInfo funcInfo) {
super(varName, varType, initialValue);
this.funcInfo = funcInfo;
}
/** Returns the descriptor of the function in which this var is defined. */
public FuncInfo getFuncInfo() {
return funcInfo;
}
}
@@ -0,0 +1,7 @@
package chocopy.common.codegen;
/**
* Abstract base class for all the Info classes that store information about a symbol during code
* generation.
*/
public abstract class SymbolInfo {}
@@ -0,0 +1,40 @@
package chocopy.common.codegen;
import chocopy.common.analysis.types.ValueType;
import chocopy.common.astnodes.Literal;
/** Information about a variable or attribute. */
public abstract class VarInfo extends SymbolInfo {
/** Name of variable or attribute. */
protected final String varName;
/** Runtime location of initial value for this variable or attribute. */
protected final Literal initialValue;
/** Static type of the variable. */
protected final ValueType varType;
/**
* A descriptor for variable or attribute VARNAME with VARTYPE as its static type and
* INITIALVALUE as its initial value (or null if None).
*/
public VarInfo(String varName, ValueType varType, Literal initialValue) {
this.varName = varName;
this.varType = varType;
this.initialValue = initialValue;
}
/** Returns the name of this variable or attribute. */
public String getVarName() {
return varName;
}
/** Returns the type of this variable or attribute. */
public ValueType getVarType() {
return varType;
}
/** Returns the initial value of this variable or attribute. */
public Literal getInitialValue() {
return initialValue;
}
}
+187
View File
@@ -0,0 +1,187 @@
package chocopy.pa3;
import chocopy.common.analysis.AbstractNodeAnalyzer;
import chocopy.common.analysis.SymbolTable;
import chocopy.common.astnodes.ReturnStmt;
import chocopy.common.astnodes.Stmt;
import chocopy.common.codegen.*;
import java.util.List;
import static chocopy.common.codegen.RiscVBackend.Register.*;
/**
* This is where the main implementation of PA3 will live.
*
* <p>A large part of the functionality has already been implemented in the base class, CodeGenBase.
* Make sure to read through that class, since you will want to use many of its fields and utility
* methods in this class when emitting code.
*
* <p>Also read the PDF spec for details on what the base class does and what APIs it exposes for
* its sub-class (this one). Of particular importance is knowing what all the SymbolInfo classes
* contain.
*/
public class CodeGenImpl extends CodeGenBase {
/** A code generator emitting instructions to BACKEND. */
public CodeGenImpl(RiscVBackend backend) {
super(backend);
}
/** Operation on None. */
private final Label errorNone = new Label("error.None");
/** Division by zero. */
private final Label errorDiv = new Label("error.Div");
/** Index out of bounds. */
private final Label errorOob = new Label("error.OOB");
/**
* Emits the top level of the program.
*
* <p>This method is invoked exactly once, and is surrounded by some boilerplate code that: (1)
* initializes the heap before the top-level begins and (2) exits after the top-level ends.
*
* <p>You only need to generate code for statements.
*
* @param statements top level statements
*/
protected void emitTopLevel(List<Stmt> statements) {
StmtAnalyzer stmtAnalyzer = new StmtAnalyzer(null);
backend.emitADDI(
SP, SP, -2 * backend.getWordSize(), "Saved FP and saved RA (unused at top level).");
backend.emitSW(ZERO, SP, 0, "Top saved FP is 0.");
backend.emitSW(ZERO, SP, 4, "Top saved RA is 0.");
backend.emitADDI(FP, SP, 2 * backend.getWordSize(), "Set FP to previous SP.");
for (Stmt stmt : statements) {
stmt.dispatch(stmtAnalyzer);
}
backend.emitLI(A0, EXIT_ECALL, "Code for ecall: exit");
backend.emitEcall(null);
}
/**
* Emits the code for a function described by FUNCINFO.
*
* <p>This method is invoked once per function and method definition. At the code generation
* stage, nested functions are emitted as separate functions of their own. So if function `bar`
* is nested within function `foo`, you only emit `foo`'s code for `foo` and only emit `bar`'s
* code for `bar`.
*/
protected void emitUserDefinedFunction(FuncInfo funcInfo) {
backend.emitGlobalLabel(funcInfo.getCodeLabel());
StmtAnalyzer stmtAnalyzer = new StmtAnalyzer(funcInfo);
for (Stmt stmt : funcInfo.getStatements()) {
stmt.dispatch(stmtAnalyzer);
}
backend.emitMV(A0, ZERO, "Returning None implicitly");
backend.emitLocalLabel(stmtAnalyzer.epilogue, "Epilogue");
// FIXME: {... reset fp etc. ...}
backend.emitJR(RA, "Return to caller");
}
/** An analyzer that encapsulates code generation for statements. */
private class StmtAnalyzer extends AbstractNodeAnalyzer<Void> {
/*
* The symbol table has all the info you need to determine
* what a given identifier 'x' in the current scope is. You can
* use it as follows:
* SymbolInfo x = sym.get("x");
*
* A SymbolInfo can be one the following:
* - ClassInfo: a descriptor for classes
* - FuncInfo: a descriptor for functions/methods
* - AttrInfo: a descriptor for attributes
* - GlobalVarInfo: a descriptor for global variables
* - StackVarInfo: a descriptor for variables allocated on the stack,
* such as locals and parameters
*
* Since the input program is assumed to be semantically
* valid and well-typed at this stage, you can always assume that
* the symbol table contains valid information. For example, in
* an expression `foo()` you KNOW that sym.get("foo") will either be
* a FuncInfo or ClassInfo, but not any of the other infos
* and never null.
*
* The symbol table in funcInfo has already been populated in
* the base class: CodeGenBase. You do not need to add anything to
* the symbol table. Simply query it with an identifier name to
* get a descriptor for a function, class, variable, etc.
*
* The symbol table also maps nonlocal and global vars, so you
* only need to lookup one symbol table and it will fetch the
* appropriate info for the var that is currently in scope.
*/
/** Symbol table for my statements. */
private final SymbolTable<SymbolInfo> sym;
/** Label of code that exits from procedure. */
protected final Label epilogue;
/** The descriptor for the current function, or null at the top level. */
private final FuncInfo funcInfo;
/** An analyzer for the function described by FUNCINFO0, which is null for the top level. */
StmtAnalyzer(FuncInfo funcInfo0) {
funcInfo = funcInfo0;
if (funcInfo == null) {
sym = globalSymbols;
} else {
sym = funcInfo.getSymbolTable();
}
epilogue = generateLocalLabel();
}
// FIXME: Example of statement.
@Override
public Void analyze(ReturnStmt stmt) {
// FIXME: Here, we emit an instruction that does nothing. Clearly,
// this is wrong, and you'll have to fix it.
// This is here just to demonstrate how to emit a
// RISC-V instruction.
backend.emitMV(ZERO, ZERO, "No-op");
return null;
}
// FIXME: More, of course.
}
/**
* Emits custom code in the CODE segment.
*
* <p>This method is called after emitting the top level and the function bodies for each
* function.
*
* <p>You can use this method to emit anything you want outside of the top level or functions,
* e.g. custom routines that you may want to call from within your code to do common tasks. This
* is not strictly needed. You might not modify this at all and still complete the assignment.
*
* <p>To start you off, here is an implementation of three routines that will be commonly needed
* from within the code you will generate for statements.
*
* <p>The routines are error handlers for operations on None, index out of bounds, and division
* by zero. They never return to their caller. Just jump to one of these routines to throw an
* error and exit the program. For example, to throw an OOB error: backend.emitJ(errorOob, "Go
* to out-of-bounds error and abort");
*/
protected void emitCustomCode() {
emitErrorFunc(errorNone, "Operation on None");
emitErrorFunc(errorDiv, "Division by zero");
emitErrorFunc(errorOob, "Index out of bounds");
}
/** Emit an error routine labeled ERRLABEL that aborts with message MSG. */
private void emitErrorFunc(Label errLabel, String msg) {
backend.emitGlobalLabel(errLabel);
backend.emitLI(A0, ERROR_NONE, "Exit code for: " + msg);
backend.emitLA(A1, constants.getStrConstant(msg), "Load error message as str");
backend.emitADDI(
A1, A1, getAttrOffset(strClass, "__str__"), "Load address of attribute __str__");
backend.emitJ(abortLabel, "Abort");
}
}
@@ -0,0 +1,34 @@
package chocopy.pa3;
import chocopy.common.astnodes.Program;
import chocopy.common.codegen.CodeGenBase;
import chocopy.common.codegen.RiscVBackend;
/** Interface to code generator. */
public class StudentCodeGen {
/**
* Perform code generation from PROGRAM, assumed to be well-typed, to RISC-V, returning the
* assembly code. DEBUG iff --debug was on the command line.
*/
public static String process(Program program, boolean debug) {
/* Emit code into a ByteOutputStream, and convert to a string.
* If you need instructions not provided by RiscVBackend, simply
* use an extension of it. */
try {
RiscVBackend backend = new RiscVBackend();
CodeGenBase cgen = new CodeGenImpl(backend);
cgen.generate(program);
return backend.toString();
} catch (IllegalStateException | IllegalArgumentException e) {
System.err.println(
"Error performing code generation. "
+ "Re-run with --debug to see stack trace.");
if (debug) {
e.printStackTrace();
}
return null;
}
}
}