commit 9f1681bb23de87471ce65951a2b7713cf43de5f9
Author: github-classroom[bot] <66690702+github-classroom[bot]@users.noreply.github.com>
Date: Sun Apr 4 18:11:58 2021 +0000
Initial commit
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..21cc758
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,149 @@
+.DS_Store
+__pycache__
+target
+
+# Created by https://www.gitignore.io/api/java,eclipse,intellij,emacs,vim
+
+### Java ###
+*.class
+
+# Mobile Tools for Java (J2ME)
+.mtj.tmp/
+
+# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
+hs_err_pid*
+
+
+### Eclipse ###
+*.pydevproject
+.metadata
+.gradle
+bin/
+tmp/
+*.tmp
+*.bak
+*.swp
+*~.nib
+local.properties
+.settings/
+.loadpath
+
+# Eclipse Core
+.project
+
+# External tool builders
+.externalToolBuilders/
+
+# Locally stored "Eclipse launch configurations"
+*.launch
+
+# CDT-specific
+.cproject
+
+# JDT-specific (Eclipse Java Development Tools)
+.classpath
+
+# Java annotation processor (APT)
+.factorypath
+
+# PDT-specific
+.buildpath
+
+# sbteclipse plugin
+.target
+
+# TeXlipse plugin
+.texlipse
+
+
+### Intellij ###
+# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio
+
+*.iml
+
+## Directory-based project format:
+.idea/
+# if you remove the above rule, at least ignore the following:
+
+# User-specific stuff:
+# .idea/workspace.xml
+# .idea/tasks.xml
+# .idea/dictionaries
+
+# Sensitive or high-churn files:
+# .idea/dataSources.ids
+# .idea/dataSources.xml
+# .idea/sqlDataSources.xml
+# .idea/dynamic.xml
+# .idea/uiDesigner.xml
+
+# Gradle:
+# .idea/gradle.xml
+# .idea/libraries
+
+# Mongo Explorer plugin:
+# .idea/mongoSettings.xml
+
+## File-based project format:
+*.ipr
+*.iws
+
+## Plugin-specific files:
+
+# IntelliJ
+/out/
+
+# mpeltonen/sbt-idea plugin
+.idea_modules/
+
+# JIRA plugin
+atlassian-ide-plugin.xml
+
+# Crashlytics plugin (for Android Studio and IntelliJ)
+com_crashlytics_export_strings.xml
+crashlytics.properties
+crashlytics-build.properties
+
+
+### Emacs ###
+# -*- mode: gitignore; -*-
+*~
+\#*\#
+/.emacs.desktop
+/.emacs.desktop.lock
+*.elc
+auto-save-list
+tramp
+.\#*
+
+# Org-mode
+.org-id-locations
+*_archive
+
+# flymake-mode
+*_flymake.*
+
+# eshell files
+/eshell/history
+/eshell/lastdir
+
+# elpa packages
+/elpa/
+
+# reftex files
+*.rel
+
+# AUCTeX auto folder
+/auto/
+
+# cask packages
+.cask/
+
+
+### Vim ###
+[._]*.s[a-w][a-z]
+[._]s[a-w][a-z]
+*.un~
+Session.vim
+.netrwhist
+*~
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..0808d4b
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,26 @@
+Copyright (c) 2017-2018 The Regents of the University of California
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+1. Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..94d677f
--- /dev/null
+++ b/README.md
@@ -0,0 +1,52 @@
+# NYU Compiler Construction CSCI-GA.2130/Spring 2021: Programming Assignment 3
+
+This assignment is adapted from https://github.com/cs164berkeley/pa3-chocopy-code-generation with the authors' permission.
+
+See the PA3 document on Piazza for a detailed specification.
+
+## Quickstart
+
+Run the following commands to compile your code generator and run the tests:
+```
+mvn clean package
+java -cp "chocopy-ref.jar:target/assignment.jar" chocopy.ChocoPy \
+ --pass=..s --test --run --dir src/test/data/pa3/sample/
+```
+
+The dots in `--pass` make the compiler skip parsing and semantic analysis and go straight to code generation.
+`--pass=..s` uses your (`s` for `student`) generator to generate code from an annotated AST (the `.ast.typed` files under `src/test/data/pa3/sample/`).
+With the starter code, only one test should pass.
+Your main objective is to build a code generator that passes all the provided tests.
+
+`--pass=..r` uses the reference (`r` for `reference`) generator, which should pass all tests.
+
+In addition to running in test mode with `--test`, you can also observe the actual output of your (or reference) generator with:
+```
+java -cp "chocopy-ref.jar:target/assignment.jar" chocopy.ChocoPy \
+ --pass=..s src/test/data/pa3/sample/op_add.py.ast.typed
+```
+
+You can also run all passes on the original `.py` file:
+```
+java -cp "chocopy-ref.jar:target/assignment.jar" chocopy.ChocoPy \
+ --pass=rrr src/test/data/pa3/sample/op_add.py
+```
+
+Once you merge your semantic analysis code from assignment 2, you should be able to use `--pass=sss`.
+
+## Generating vs Running
+
+The above should be familiar from previous assignments.
+A new aspect of PA3 is the distinction between generating and running the code.
+
+The following outputs the generated RISC-V assembly (with the usual option to save to a file with `--out`):
+```
+java -cp "chocopy-ref.jar:target/assignment.jar" chocopy.ChocoPy \
+ --pass=rrr src/test/data/pa3/sample/op_add.py
+```
+
+The following (note the `--run` option) generates the assembly and then _runs it on the bundled RISC-V emulator_:
+```
+java -cp "chocopy-ref.jar:target/assignment.jar" chocopy.ChocoPy \
+ --pass=rrr --run src/test/data/pa3/sample/op_add.py
+```
diff --git a/chocopy-ref.jar b/chocopy-ref.jar
new file mode 100644
index 0000000..d036a97
Binary files /dev/null and b/chocopy-ref.jar differ
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..72867df
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,330 @@
+
+ 4.0.0
+ chocopy
+ chocopy
+ jar
+ 2.2-SNAPSHOT
+ chocopy
+ http://maven.apache.org
+
+
+
+ false
+
+
+
+
+
+
+
+ de.jflex
+ jflex-maven-plugin
+ 1.6.1
+
+
+ com.github.vbmacher
+ cup-maven-plugin
+ 11b-20160615
+
+
+ maven-assembly-plugin
+ 3.1.0
+
+
+ maven-jar-plugin
+ 3.1.0
+
+
+ maven-site-plugin
+ 3.7.1
+
+
+
+
+
+
+ maven-assembly-plugin
+
+
+ jar-with-dependencies
+
+ assignment
+ false
+
+
+
+ make-assembly
+ package
+
+ single
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.8.0
+
+ UTF-8
+
+ 1.8
+ true
+ true
+
+
+
+
+
+
+ src/main/asm
+
+ **/*.s
+ **/*.os
+
+
+ **/reference/*.s
+
+
+
+
+
+
+
+ reference
+
+
+
+ src/main/java/chocopy/reference/
+
+
+
+
+
+
+ maven-jar-plugin
+
+
+ **/pa1/*
+ **/pa2/*
+ **/pa3/*
+
+
+
+
+
+ maven-assembly-plugin
+
+
+ jar-with-dependencies
+
+ chocopy-ref
+ false
+
+
+
+
+ de.jflex
+ jflex-maven-plugin
+
+
+ jflex-reference
+
+ generate
+
+
+
+ src/main/jflex/chocopy/reference/ChocoPy.jflex
+
+ ${chocopy.debug}
+ true
+
+
+
+
+
+
+ com.github.vbmacher
+ cup-maven-plugin
+
+
+ cup-reference
+
+ generate
+
+
+ src/main/cup/chocopy/reference/ChocoPy.cup
+ chocopy.reference
+ ChocoPyParser
+ ChocoPyTokens
+ ${chocopy.debug}
+ ${chocopy.debug}
+ ${chocopy.debug}
+ true
+
+
+
+
+
+
+ maven-compiler-plugin
+
+ true
+ none
+
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+ 3.1.1
+
+
+ copy-dependencies
+ package
+
+ copy-dependencies
+
+
+
+
+
+
+
+
+
+ pa1
+
+
+
+ src/main/java/chocopy/pa1
+
+
+
+
+
+
+ de.jflex
+ jflex-maven-plugin
+
+
+ jflex-pa1
+
+ generate
+
+
+
+ src/main/jflex/chocopy/pa1/ChocoPy.jflex
+
+ ${chocopy.debug}
+ true
+
+
+
+
+
+
+ com.github.vbmacher
+ cup-maven-plugin
+
+
+ cup-pa1
+
+ generate
+
+
+ src/main/cup/chocopy/pa1/ChocoPy.cup
+ chocopy.pa1
+ ChocoPyParser
+ ChocoPyTokens
+ ${chocopy.debug}
+ ${chocopy.debug}
+ ${chocopy.debug}
+ true
+
+
+
+
+
+
+
+
+ pa2
+
+
+
+ src/main/java/chocopy/pa2
+
+
+
+
+ pa3
+
+
+
+ src/main/java/chocopy/pa3
+
+
+
+
+
+
+
+
+ venus164-repo
+ Repository for Venus164
+ https://raw.githubusercontent.com/chocopy/venus/maven-repository/
+
+
+
+
+
+ net.sourceforge.argparse4j
+ argparse4j
+ 0.8.1
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+ 2.9.10
+
+
+ com.fasterxml.jackson.module
+ jackson-module-parameter-names
+ 2.9.10
+
+
+ com.github.vbmacher
+ java-cup-runtime
+ 11b-20160615
+
+
+
+ de.jflex
+ jflex
+ 1.6.1
+
+
+ org.jetbrains.kotlin
+ kotlin-stdlib
+ 1.2.71
+
+
+ edu.berkeley.eecs.venus164
+ venus164
+ 0.2.4
+
+
+ junit
+ junit
+ 4.12
+ test
+
+
+ net.sf.proguard
+ proguard-base
+ 6.0.3
+
+
+
diff --git a/src/main/asm/chocopy/common/abort.s b/src/main/asm/chocopy/common/abort.s
new file mode 100644
index 0000000..fa8148a
--- /dev/null
+++ b/src/main/asm/chocopy/common/abort.s
@@ -0,0 +1,12 @@
+# Runtime support function abort (does not return).
+ mv t0, a0 # Save exit code in temp
+ li a0, @print_string # Code for print_string ecall
+ ecall # Print error message in a1
+ li a1, 10 # Load newline character
+ li a0, @print_char # Code for print_char ecall
+ ecall # Print newline
+ mv a1, t0 # Move exit code to a1
+ li a0, @exit2 # Code for exit2 ecall
+ ecall # Exit with code
+abort_17: # Infinite loop
+ j abort_17 # Prevent fallthrough
diff --git a/src/main/asm/chocopy/common/alloc.s b/src/main/asm/chocopy/common/alloc.s
new file mode 100644
index 0000000..da54abe
--- /dev/null
+++ b/src/main/asm/chocopy/common/alloc.s
@@ -0,0 +1,4 @@
+# Runtime support function alloc.
+ # Prototype address is in a0.
+ lw a1, 4(a0) # Get size of object in words
+ j alloc2 # Allocate object with exact size
diff --git a/src/main/asm/chocopy/common/alloc2.s b/src/main/asm/chocopy/common/alloc2.s
new file mode 100644
index 0000000..9540512
--- /dev/null
+++ b/src/main/asm/chocopy/common/alloc2.s
@@ -0,0 +1,27 @@
+# Runtime support function alloc2 (realloc).
+ # Prototype address is in a0.
+ # Number of words to allocate is in a1.
+ li a2, 4 # Word size in bytes
+ mul a2, a1, a2 # Calculate number of bytes to allocate
+ add a2, gp, a2 # Estimate where GP will move
+ bgeu a2, s11, alloc2_15 # Go to OOM handler if too large
+ lw t0, @.__obj_size__(a0) # Get size of object in words
+ mv t2, a0 # Initialize src ptr
+ mv t3, gp # Initialize dest ptr
+alloc2_16: # Copy-loop header
+ lw t1, 0(t2) # Load next word from src
+ sw t1, 0(t3) # Store next word to dest
+ addi t2, t2, 4 # Increment src
+ addi t3, t3, 4 # Increment dest
+ addi t0, t0, -1 # Decrement counter
+ bne t0, zero, alloc2_16 # Loop if more words left to copy
+ mv a0, gp # Save new object's address to return
+ sw a1, @.__obj_size__(a0) # Set size of new object in words
+ # (same as requested size)
+ mv gp, a2 # Set next free slot in the heap
+ jr ra # Return to caller
+alloc2_15: # OOM handler
+ li a0, @error_oom # Exit code for: Out of memory
+ la a1, STRING["Out of memory"] # Load error message as str
+ addi a1, a1, @.__str__ # Load address of attribute __str__
+ j abort # Abort
diff --git a/src/main/asm/chocopy/common/heap.init.s b/src/main/asm/chocopy/common/heap.init.s
new file mode 100644
index 0000000..5dfe9a8
--- /dev/null
+++ b/src/main/asm/chocopy/common/heap.init.s
@@ -0,0 +1,5 @@
+# Runtime support function heap.init.
+ mv a1, a0 # Move requested size to A1
+ li a0, @sbrk # Code for ecall: sbrk
+ ecall # Request A1 bytes
+ jr ra # Return to caller
diff --git a/src/main/asm/chocopy/common/input.s b/src/main/asm/chocopy/common/input.s
new file mode 100644
index 0000000..e48d97a
--- /dev/null
+++ b/src/main/asm/chocopy/common/input.s
@@ -0,0 +1,40 @@
+# Function input
+ addi sp, sp, -16 # Reserve stack
+ sw ra, 12(sp) # Save registers
+ sw fp, 8(sp)
+ sw s1, 4(sp)
+ addi fp, sp, 16 # Set fp
+
+ li a0, @fill_line_buffer # Fill the internal line buffer.
+ ecall
+ bgez a0, input_nonempty # More input found
+ la a0, $str$prototype # EOF: Return empty string.
+ j input_done
+
+input_nonempty:
+ mv s1, a0
+ addi t0, s1, 5 # Compute bytes for string (+NL+NUL),
+ addi t0, t0, @.__str__ # Including header.
+ srli a1, t0, 2 # Convert to words.
+ la a0, $str$prototype # Load address of string prototype.
+ jal ra, alloc2 # Allocate string.
+ sw s1, @.__len__(a0) # Store string length.
+ mv a2, s1 # Pass length.
+ mv s1, a0 # Save string object address.
+ addi a1, a0, @.__str__ # Pass address of string data.
+ li a0, @read_string # ecall to read from internal buffer.
+ ecall
+ addi a0, a0, 1 # Actual length (including NL).
+ sw a0, @.__len__(s1) # Store actual length.
+ add t0, a0, s1
+ li t1, 10 # Store newline and null byte
+ sb t1, @.__str__-1(t0)
+ sb zero, @.__str__(t0) # Store null byte at end.
+ mv a0, s1 # Return string object.
+
+input_done:
+ lw s1, -12(fp)
+ lw ra, -4(fp)
+ lw fp, -8(fp)
+ addi sp, sp, 16
+ jr ra
\ No newline at end of file
diff --git a/src/main/asm/chocopy/common/len.s b/src/main/asm/chocopy/common/len.s
new file mode 100644
index 0000000..72e9bea
--- /dev/null
+++ b/src/main/asm/chocopy/common/len.s
@@ -0,0 +1,20 @@
+# Function len
+ # We do not save/restore fp/ra for this function
+ # because we know that it does not use the stack or does not
+ # call other functions.
+
+ lw a0, 0(sp) # Load arg
+ beq a0, zero, len_12 # None is an illegal argument
+ lw t0, 0(a0) # Get type tag of arg
+ li t1, 3 # Load type tag of `str`
+ beq t0, t1, len_13 # Go to len(str)
+ li t1, -1 # Load type tag for list objects
+ beq t0, t1, len_13 # Go to len(list)
+len_12: # Invalid argument
+ li a0, @error_arg # Exit code for: Invalid argument
+ la a1, STRING["Invalid argument"] # Load error message as str
+ addi a1, a1, @.__str__ # Load address of attribute __str__
+ j abort # Abort
+len_13: # Get length of string
+ lw a0, @.__len__(a0) # Load attribute: __len__
+ jr ra # Return to caller
diff --git a/src/main/asm/chocopy/common/object.__init__.s b/src/main/asm/chocopy/common/object.__init__.s
new file mode 100644
index 0000000..e326950
--- /dev/null
+++ b/src/main/asm/chocopy/common/object.__init__.s
@@ -0,0 +1,3 @@
+# Init method for type object.
+ mv a0, zero # `None` constant
+ jr ra # Return
diff --git a/src/main/asm/chocopy/common/print.s b/src/main/asm/chocopy/common/print.s
new file mode 100644
index 0000000..4a9d376
--- /dev/null
+++ b/src/main/asm/chocopy/common/print.s
@@ -0,0 +1,52 @@
+# Function print
+ lw a0, 0(sp) # Load arg
+ beq a0, zero, print_6 # None is an illegal argument
+ lw t0, 0(a0) # Get type tag of arg
+ li t1, 1 # Load type tag of `int`
+ beq t0, t1, print_7 # Go to print(int)
+ li t1, 3 # Load type tag of `str`
+ beq t0, t1, print_8 # Go to print(str)
+ li t1, 2 # Load type tag of `bool`
+ beq t0, t1, print_9 # Go to print(bool)
+print_6: # Invalid argument
+ li a0, 1 # Exit code for: Invalid argument
+ la a1, STRING["Invalid argument"] # Load error message as str
+ addi a1, a1, @.__str__ # Load address of attribute __str__
+ j abort # Abort
+
+# Printing bools
+print_9: # Print bool object in A0
+ lw a0, @.__bool__(a0) # Load attribute __bool__
+ beq a0, zero, print_10 # Go to: print(False)
+ la a0, STRING["True"] # String representation: True
+ j print_8 # Go to: print(str)
+print_10: # Print False object in A0
+ la a0, STRING["False"] # String representation: False
+ j print_8 # Go to: print(str)
+
+# Printing strs.
+print_8: # Print str object in A0
+ addi a1, a0, @.__str__ # Load address of attribute __str__
+ j print_11 # Print the null-terminated string is now in A1
+ mv a0, zero # Load None
+ j print_5 # Go to return
+print_11: # Print null-terminated string in A1
+ li a0, @print_string # Code for ecall: print_string
+ ecall # Print string
+ li a1, 10 # Load newline character
+ li a0, @print_char # Code for ecall: print_char
+ ecall # Print character
+ j print_5 # Go to return
+
+# Printing ints.
+print_7: # Print int object in A0
+ lw a1, @.__int__(a0) # Load attribute __int__
+ li a0, @print_int # Code for ecall: print_int
+ ecall # Print integer
+ li a1, 10 # Load newline character
+ li a0, 11 # Code for ecall: print_char
+ ecall # Print character
+
+print_5: # End of function
+ mv a0, zero # Load None
+ jr ra # Return to caller
diff --git a/src/main/java/chocopy/common/Utils.java b/src/main/java/chocopy/common/Utils.java
new file mode 100644
index 0000000..41360cc
--- /dev/null
+++ b/src/main/java/chocopy/common/Utils.java
@@ -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.
+ *
+ *
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();
+ }
+}
diff --git a/src/main/java/chocopy/common/analysis/AbstractNodeAnalyzer.java b/src/main/java/chocopy/common/analysis/AbstractNodeAnalyzer.java
new file mode 100644
index 0000000..7225c2b
--- /dev/null
+++ b/src/main/java/chocopy/common/analysis/AbstractNodeAnalyzer.java
@@ -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.
+ *
+ *
T is the type of analysis result.
+ */
+public class AbstractNodeAnalyzer implements NodeAnalyzer {
+ @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;
+}
diff --git a/src/main/java/chocopy/common/analysis/NodeAnalyzer.java b/src/main/java/chocopy/common/analysis/NodeAnalyzer.java
new file mode 100644
index 0000000..2326fbc
--- /dev/null
+++ b/src/main/java/chocopy/common/analysis/NodeAnalyzer.java
@@ -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.
+ *
+ *
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 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.
+ *
+ *
The class AbstractNodeAnalyzer provides empty default implementations for these methods.
+ *
+ *
The type T is the type of result returned by the encapsulated analysis.
+ */
+public interface NodeAnalyzer {
+
+ 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);
+}
diff --git a/src/main/java/chocopy/common/analysis/SymbolTable.java b/src/main/java/chocopy/common/analysis/SymbolTable.java
new file mode 100644
index 0000000..bc22ac4
--- /dev/null
+++ b/src/main/java/chocopy/common/analysis/SymbolTable.java
@@ -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 {
+
+ /** Contents of the current (innermost) region. */
+ private final Map tab = new HashMap<>();
+ /** Enclosing block. */
+ private final SymbolTable parent;
+
+ /** A table representing a region nested in that represented by PARENT0. */
+ public SymbolTable(SymbolTable 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 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 getDeclaredSymbols() {
+ return tab.keySet();
+ }
+
+ /** Returns the parent, or null if this is the top level. */
+ public SymbolTable getParent() {
+ return this.parent;
+ }
+}
diff --git a/src/main/java/chocopy/common/analysis/types/ClassValueType.java b/src/main/java/chocopy/common/analysis/types/ClassValueType.java
new file mode 100644
index 0000000..05c2e93
--- /dev/null
+++ b/src/main/java/chocopy/common/analysis/types/ClassValueType.java
@@ -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;
+ }
+}
diff --git a/src/main/java/chocopy/common/analysis/types/FuncType.java b/src/main/java/chocopy/common/analysis/types/FuncType.java
new file mode 100644
index 0000000..69fdefc
--- /dev/null
+++ b/src/main/java/chocopy/common/analysis/types/FuncType.java
@@ -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 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 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 "";
+ }
+}
diff --git a/src/main/java/chocopy/common/analysis/types/ListValueType.java b/src/main/java/chocopy/common/analysis/types/ListValueType.java
new file mode 100644
index 0000000..fae59c7
--- /dev/null
+++ b/src/main/java/chocopy/common/analysis/types/ListValueType.java
@@ -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 [], where 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;
+ }
+}
diff --git a/src/main/java/chocopy/common/analysis/types/Type.java b/src/main/java/chocopy/common/analysis/types/Type.java
new file mode 100644
index 0000000..2eddc37
--- /dev/null
+++ b/src/main/java/chocopy/common/analysis/types/Type.java
@@ -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.
+ *
+ *
Symbols such as variables and attributes will typically map to a {@link ValueType}.
+ *
+ *
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("");
+ /** The type of []. */
+ public static final ClassValueType EMPTY_TYPE = new ClassValueType("");
+
+ /** 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;
+ }
+}
diff --git a/src/main/java/chocopy/common/analysis/types/ValueType.java b/src/main/java/chocopy/common/analysis/types/ValueType.java
new file mode 100644
index 0000000..bb5d908
--- /dev/null
+++ b/src/main/java/chocopy/common/analysis/types/ValueType.java
@@ -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.
+ *
+ *
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;
+ }
+}
diff --git a/src/main/java/chocopy/common/astnodes/AssignStmt.java b/src/main/java/chocopy/common/astnodes/AssignStmt.java
new file mode 100644
index 0000000..1452463
--- /dev/null
+++ b/src/main/java/chocopy/common/astnodes/AssignStmt.java
@@ -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 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 targets, Expr value) {
+ super(left, right);
+ this.targets = targets;
+ this.value = value;
+ }
+
+ public T dispatch(NodeAnalyzer analyzer) {
+ return analyzer.analyze(this);
+ }
+}
diff --git a/src/main/java/chocopy/common/astnodes/BinaryExpr.java b/src/main/java/chocopy/common/astnodes/BinaryExpr.java
new file mode 100644
index 0000000..f4ce05b
--- /dev/null
+++ b/src/main/java/chocopy/common/astnodes/BinaryExpr.java
@@ -0,0 +1,31 @@
+package chocopy.common.astnodes;
+
+import chocopy.common.analysis.NodeAnalyzer;
+import java_cup.runtime.ComplexSymbolFactory.Location;
+
+/** . */
+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 dispatch(NodeAnalyzer analyzer) {
+ return analyzer.analyze(this);
+ }
+}
diff --git a/src/main/java/chocopy/common/astnodes/BooleanLiteral.java b/src/main/java/chocopy/common/astnodes/BooleanLiteral.java
new file mode 100644
index 0000000..ffce126
--- /dev/null
+++ b/src/main/java/chocopy/common/astnodes/BooleanLiteral.java
@@ -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 dispatch(NodeAnalyzer analyzer) {
+ return analyzer.analyze(this);
+ }
+}
diff --git a/src/main/java/chocopy/common/astnodes/CallExpr.java b/src/main/java/chocopy/common/astnodes/CallExpr.java
new file mode 100644
index 0000000..84f13ae
--- /dev/null
+++ b/src/main/java/chocopy/common/astnodes/CallExpr.java
@@ -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 args;
+
+ /** AST for FUNCTION(ARGS) at [LEFT..RIGHT]. */
+ public CallExpr(Location left, Location right, Identifier function, List args) {
+ super(left, right);
+ this.function = function;
+ this.args = args;
+ }
+
+ public T dispatch(NodeAnalyzer analyzer) {
+ return analyzer.analyze(this);
+ }
+}
diff --git a/src/main/java/chocopy/common/astnodes/ClassDef.java b/src/main/java/chocopy/common/astnodes/ClassDef.java
new file mode 100644
index 0000000..63531c8
--- /dev/null
+++ b/src/main/java/chocopy/common/astnodes/ClassDef.java
@@ -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 declarations;
+
+ /** An AST for class NAME(SUPERCLASS): DECLARATIONS. spanning source locations [LEFT..RIGHT]. */
+ public ClassDef(
+ Location left,
+ Location right,
+ Identifier name,
+ Identifier superClass,
+ List declarations) {
+ super(left, right);
+ this.name = name;
+ this.superClass = superClass;
+ this.declarations = declarations;
+ }
+
+ public T dispatch(NodeAnalyzer analyzer) {
+ return analyzer.analyze(this);
+ }
+
+ @Override
+ public Identifier getIdentifier() {
+ return this.name;
+ }
+}
diff --git a/src/main/java/chocopy/common/astnodes/ClassType.java b/src/main/java/chocopy/common/astnodes/ClassType.java
new file mode 100644
index 0000000..fd4cd4d
--- /dev/null
+++ b/src/main/java/chocopy/common/astnodes/ClassType.java
@@ -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 dispatch(NodeAnalyzer analyzer) {
+ return analyzer.analyze(this);
+ }
+}
diff --git a/src/main/java/chocopy/common/astnodes/CompilerError.java b/src/main/java/chocopy/common/astnodes/CompilerError.java
new file mode 100644
index 0000000..81cc5bb
--- /dev/null
+++ b/src/main/java/chocopy/common/astnodes/CompilerError.java
@@ -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 dispatch(NodeAnalyzer analyzer) {
+ return analyzer.analyze(this);
+ }
+
+ /** The error message. */
+ public final String message;
+ /** True if this is a syntax error. */
+ private final boolean syntax;
+}
diff --git a/src/main/java/chocopy/common/astnodes/Declaration.java b/src/main/java/chocopy/common/astnodes/Declaration.java
new file mode 100644
index 0000000..0faa921
--- /dev/null
+++ b/src/main/java/chocopy/common/astnodes/Declaration.java
@@ -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();
+}
diff --git a/src/main/java/chocopy/common/astnodes/Errors.java b/src/main/java/chocopy/common/astnodes/Errors.java
new file mode 100644
index 0000000..86240dd
--- /dev/null
+++ b/src/main/java/chocopy/common/astnodes/Errors.java
@@ -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 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 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 dispatch(NodeAnalyzer analyzer) {
+ return analyzer.analyze(this);
+ }
+}
diff --git a/src/main/java/chocopy/common/astnodes/Expr.java b/src/main/java/chocopy/common/astnodes/Expr.java
new file mode 100644
index 0000000..7372d58
--- /dev/null
+++ b/src/main/java/chocopy/common/astnodes/Expr.java
@@ -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.
+ *
+ *
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.
+ *
+ *
This field is always null after the parsing stage, but is populated by the
+ * typechecker in the semantic analysis stage.
+ *
+ *
After typechecking this field may be null 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;
+ }
+}
diff --git a/src/main/java/chocopy/common/astnodes/ExprStmt.java b/src/main/java/chocopy/common/astnodes/ExprStmt.java
new file mode 100644
index 0000000..5b921a0
--- /dev/null
+++ b/src/main/java/chocopy/common/astnodes/ExprStmt.java
@@ -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 dispatch(NodeAnalyzer analyzer) {
+ return analyzer.analyze(this);
+ }
+}
diff --git a/src/main/java/chocopy/common/astnodes/ForStmt.java b/src/main/java/chocopy/common/astnodes/ForStmt.java
new file mode 100644
index 0000000..019b2d8
--- /dev/null
+++ b/src/main/java/chocopy/common/astnodes/ForStmt.java
@@ -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 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 body) {
+ super(left, right);
+ this.identifier = identifier;
+ this.iterable = iterable;
+ this.body = body;
+ }
+
+ public T dispatch(NodeAnalyzer analyzer) {
+ return analyzer.analyze(this);
+ }
+}
diff --git a/src/main/java/chocopy/common/astnodes/FuncDef.java b/src/main/java/chocopy/common/astnodes/FuncDef.java
new file mode 100644
index 0000000..b3ca0f2
--- /dev/null
+++ b/src/main/java/chocopy/common/astnodes/FuncDef.java
@@ -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 params;
+ /** Return type annotation. */
+ public final TypeAnnotation returnType;
+ /** Local-variable,inner-function, global, and nonlocal declarations. */
+ public final List declarations;
+ /** Other statements. */
+ public final List statements;
+
+ /**
+ * The AST for def NAME(PARAMS) -> RETURNTYPE: DECLARATIONS STATEMENTS spanning source locations
+ * [LEFT..RIGHT].
+ */
+ public FuncDef(
+ Location left,
+ Location right,
+ Identifier name,
+ List params,
+ TypeAnnotation returnType,
+ List declarations,
+ List statements) {
+ super(left, right);
+ this.name = name;
+ this.params = params;
+ this.returnType = returnType;
+ this.declarations = declarations;
+ this.statements = statements;
+ }
+
+ public T dispatch(NodeAnalyzer analyzer) {
+ return analyzer.analyze(this);
+ }
+
+ @Override
+ public Identifier getIdentifier() {
+ return this.name;
+ }
+}
diff --git a/src/main/java/chocopy/common/astnodes/GlobalDecl.java b/src/main/java/chocopy/common/astnodes/GlobalDecl.java
new file mode 100644
index 0000000..b347d4d
--- /dev/null
+++ b/src/main/java/chocopy/common/astnodes/GlobalDecl.java
@@ -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 dispatch(NodeAnalyzer analyzer) {
+ return analyzer.analyze(this);
+ }
+
+ @Override
+ public Identifier getIdentifier() {
+ return this.variable;
+ }
+}
diff --git a/src/main/java/chocopy/common/astnodes/Identifier.java b/src/main/java/chocopy/common/astnodes/Identifier.java
new file mode 100644
index 0000000..7593126
--- /dev/null
+++ b/src/main/java/chocopy/common/astnodes/Identifier.java
@@ -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 dispatch(NodeAnalyzer analyzer) {
+ return analyzer.analyze(this);
+ }
+}
diff --git a/src/main/java/chocopy/common/astnodes/IfExpr.java b/src/main/java/chocopy/common/astnodes/IfExpr.java
new file mode 100644
index 0000000..3d3d809
--- /dev/null
+++ b/src/main/java/chocopy/common/astnodes/IfExpr.java
@@ -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 dispatch(NodeAnalyzer analyzer) {
+ return analyzer.analyze(this);
+ }
+}
diff --git a/src/main/java/chocopy/common/astnodes/IfStmt.java b/src/main/java/chocopy/common/astnodes/IfStmt.java
new file mode 100644
index 0000000..185b8a1
--- /dev/null
+++ b/src/main/java/chocopy/common/astnodes/IfStmt.java
@@ -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 thenBody;
+ /** "False" branch. */
+ public final List elseBody;
+
+ /**
+ * The AST for if CONDITION: THENBODY else: ELSEBODY spanning source locations [LEFT..RIGHT].
+ */
+ public IfStmt(
+ Location left,
+ Location right,
+ Expr condition,
+ List thenBody,
+ List elseBody) {
+ super(left, right);
+ this.condition = condition;
+ this.thenBody = thenBody;
+ this.elseBody = elseBody;
+ }
+
+ public T dispatch(NodeAnalyzer analyzer) {
+ return analyzer.analyze(this);
+ }
+}
diff --git a/src/main/java/chocopy/common/astnodes/IndexExpr.java b/src/main/java/chocopy/common/astnodes/IndexExpr.java
new file mode 100644
index 0000000..ce788fa
--- /dev/null
+++ b/src/main/java/chocopy/common/astnodes/IndexExpr.java
@@ -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 dispatch(NodeAnalyzer analyzer) {
+ return analyzer.analyze(this);
+ }
+}
diff --git a/src/main/java/chocopy/common/astnodes/IntegerLiteral.java b/src/main/java/chocopy/common/astnodes/IntegerLiteral.java
new file mode 100644
index 0000000..f2d165b
--- /dev/null
+++ b/src/main/java/chocopy/common/astnodes/IntegerLiteral.java
@@ -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 dispatch(NodeAnalyzer analyzer) {
+ return analyzer.analyze(this);
+ }
+}
diff --git a/src/main/java/chocopy/common/astnodes/ListExpr.java b/src/main/java/chocopy/common/astnodes/ListExpr.java
new file mode 100644
index 0000000..eab6045
--- /dev/null
+++ b/src/main/java/chocopy/common/astnodes/ListExpr.java
@@ -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 elements;
+
+ /** The AST for [ ELEMENTS ]. spanning source locations [LEFT..RIGHT]. */
+ public ListExpr(Location left, Location right, List elements) {
+ super(left, right);
+ this.elements = elements;
+ }
+
+ public T dispatch(NodeAnalyzer analyzer) {
+ return analyzer.analyze(this);
+ }
+}
diff --git a/src/main/java/chocopy/common/astnodes/ListType.java b/src/main/java/chocopy/common/astnodes/ListType.java
new file mode 100644
index 0000000..32782ad
--- /dev/null
+++ b/src/main/java/chocopy/common/astnodes/ListType.java
@@ -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 dispatch(NodeAnalyzer analyzer) {
+ return analyzer.analyze(this);
+ }
+}
diff --git a/src/main/java/chocopy/common/astnodes/Literal.java b/src/main/java/chocopy/common/astnodes/Literal.java
new file mode 100644
index 0000000..2afd1be
--- /dev/null
+++ b/src/main/java/chocopy/common/astnodes/Literal.java
@@ -0,0 +1,16 @@
+package chocopy.common.astnodes;
+
+import java_cup.runtime.ComplexSymbolFactory.Location;
+
+/**
+ * Base of all the literal nodes.
+ *
+ *
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);
+ }
+}
diff --git a/src/main/java/chocopy/common/astnodes/MemberExpr.java b/src/main/java/chocopy/common/astnodes/MemberExpr.java
new file mode 100644
index 0000000..a9e0a4f
--- /dev/null
+++ b/src/main/java/chocopy/common/astnodes/MemberExpr.java
@@ -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 dispatch(NodeAnalyzer analyzer) {
+ return analyzer.analyze(this);
+ }
+}
diff --git a/src/main/java/chocopy/common/astnodes/MethodCallExpr.java b/src/main/java/chocopy/common/astnodes/MethodCallExpr.java
new file mode 100644
index 0000000..ab3f424
--- /dev/null
+++ b/src/main/java/chocopy/common/astnodes/MethodCallExpr.java
@@ -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 args;
+
+ /** The AST for METHOD(ARGS). spanning source locations [LEFT..RIGHT]. */
+ public MethodCallExpr(Location left, Location right, MemberExpr method, List args) {
+ super(left, right);
+ this.method = method;
+ this.args = args;
+ }
+
+ public T dispatch(NodeAnalyzer analyzer) {
+ return analyzer.analyze(this);
+ }
+}
diff --git a/src/main/java/chocopy/common/astnodes/Node.java b/src/main/java/chocopy/common/astnodes/Node.java
new file mode 100644
index 0000000..c95f3d6
--- /dev/null
+++ b/src/main/java/chocopy/common/astnodes/Node.java
@@ -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.
+ *
+ *
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 { , , , }.
+ * 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 dispatch(NodeAnalyzer 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 fromJSON(String json, Class 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 fromJSON(JsonNode tree, Class 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);
+ }
+}
diff --git a/src/main/java/chocopy/common/astnodes/NonLocalDecl.java b/src/main/java/chocopy/common/astnodes/NonLocalDecl.java
new file mode 100644
index 0000000..ebaf78d
--- /dev/null
+++ b/src/main/java/chocopy/common/astnodes/NonLocalDecl.java
@@ -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 dispatch(NodeAnalyzer analyzer) {
+ return analyzer.analyze(this);
+ }
+
+ @Override
+ public Identifier getIdentifier() {
+ return this.variable;
+ }
+}
diff --git a/src/main/java/chocopy/common/astnodes/NoneLiteral.java b/src/main/java/chocopy/common/astnodes/NoneLiteral.java
new file mode 100644
index 0000000..b51a581
--- /dev/null
+++ b/src/main/java/chocopy/common/astnodes/NoneLiteral.java
@@ -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 dispatch(NodeAnalyzer analyzer) {
+ return analyzer.analyze(this);
+ }
+}
diff --git a/src/main/java/chocopy/common/astnodes/Program.java b/src/main/java/chocopy/common/astnodes/Program.java
new file mode 100644
index 0000000..f2d7f75
--- /dev/null
+++ b/src/main/java/chocopy/common/astnodes/Program.java
@@ -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 declarations;
+ /** Trailing statements. */
+ public final List statements;
+ /** Accumulated errors. */
+ public final Errors errors;
+
+ /**
+ * The AST for the program DECLARATIONS STATEMENTS spanning source locations [LEFT..RIGHT].
+ *
+ *
ERRORS is the container for all error messages applying to the program.
+ */
+ public Program(
+ Location left,
+ Location right,
+ List declarations,
+ List 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 dispatch(NodeAnalyzer 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 getErrorList() {
+ return errors.errors;
+ }
+}
diff --git a/src/main/java/chocopy/common/astnodes/ReturnStmt.java b/src/main/java/chocopy/common/astnodes/ReturnStmt.java
new file mode 100644
index 0000000..e665042
--- /dev/null
+++ b/src/main/java/chocopy/common/astnodes/ReturnStmt.java
@@ -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 dispatch(NodeAnalyzer analyzer) {
+ return analyzer.analyze(this);
+ }
+}
diff --git a/src/main/java/chocopy/common/astnodes/Stmt.java b/src/main/java/chocopy/common/astnodes/Stmt.java
new file mode 100644
index 0000000..b87780e
--- /dev/null
+++ b/src/main/java/chocopy/common/astnodes/Stmt.java
@@ -0,0 +1,18 @@
+package chocopy.common.astnodes;
+
+import java_cup.runtime.ComplexSymbolFactory.Location;
+
+/**
+ * Base of all AST nodes representing statements.
+ *
+ *
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);
+ }
+}
diff --git a/src/main/java/chocopy/common/astnodes/StringLiteral.java b/src/main/java/chocopy/common/astnodes/StringLiteral.java
new file mode 100644
index 0000000..0eaf928
--- /dev/null
+++ b/src/main/java/chocopy/common/astnodes/StringLiteral.java
@@ -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 dispatch(NodeAnalyzer analyzer) {
+ return analyzer.analyze(this);
+ }
+}
diff --git a/src/main/java/chocopy/common/astnodes/TypeAnnotation.java b/src/main/java/chocopy/common/astnodes/TypeAnnotation.java
new file mode 100644
index 0000000..d90b59e
--- /dev/null
+++ b/src/main/java/chocopy/common/astnodes/TypeAnnotation.java
@@ -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);
+ }
+}
diff --git a/src/main/java/chocopy/common/astnodes/TypedVar.java b/src/main/java/chocopy/common/astnodes/TypedVar.java
new file mode 100644
index 0000000..98aae43
--- /dev/null
+++ b/src/main/java/chocopy/common/astnodes/TypedVar.java
@@ -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 dispatch(NodeAnalyzer analyzer) {
+ return analyzer.analyze(this);
+ }
+}
diff --git a/src/main/java/chocopy/common/astnodes/UnaryExpr.java b/src/main/java/chocopy/common/astnodes/UnaryExpr.java
new file mode 100644
index 0000000..199e701
--- /dev/null
+++ b/src/main/java/chocopy/common/astnodes/UnaryExpr.java
@@ -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 dispatch(NodeAnalyzer analyzer) {
+ return analyzer.analyze(this);
+ }
+}
diff --git a/src/main/java/chocopy/common/astnodes/VarDef.java b/src/main/java/chocopy/common/astnodes/VarDef.java
new file mode 100644
index 0000000..61d261a
--- /dev/null
+++ b/src/main/java/chocopy/common/astnodes/VarDef.java
@@ -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 dispatch(NodeAnalyzer analyzer) {
+ return analyzer.analyze(this);
+ }
+
+ /** The identifier defined by this declaration. */
+ @Override
+ public Identifier getIdentifier() {
+ return this.var.identifier;
+ }
+}
diff --git a/src/main/java/chocopy/common/astnodes/WhileStmt.java b/src/main/java/chocopy/common/astnodes/WhileStmt.java
new file mode 100644
index 0000000..9a521e1
--- /dev/null
+++ b/src/main/java/chocopy/common/astnodes/WhileStmt.java
@@ -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 body;
+
+ /** The AST for while CONDITION: BODY spanning source locations [LEFT..RIGHT]. */
+ public WhileStmt(Location left, Location right, Expr condition, List body) {
+ super(left, right);
+ this.condition = condition;
+ this.body = body;
+ }
+
+ public T dispatch(NodeAnalyzer analyzer) {
+ return analyzer.analyze(this);
+ }
+}
diff --git a/src/main/java/chocopy/common/codegen/AttrInfo.java b/src/main/java/chocopy/common/codegen/AttrInfo.java
new file mode 100644
index 0000000..ab576e8
--- /dev/null
+++ b/src/main/java/chocopy/common/codegen/AttrInfo.java
@@ -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);
+ }
+}
diff --git a/src/main/java/chocopy/common/codegen/ClassInfo.java b/src/main/java/chocopy/common/codegen/ClassInfo.java
new file mode 100644
index 0000000..7591e96
--- /dev/null
+++ b/src/main/java/chocopy/common/codegen/ClassInfo.java
@@ -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 attributes;
+ /** Information about methods of the class. */
+ public final List 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.
+ *
+ *
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.
+ *
+ *
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 getAttributes() {
+ return attributes;
+ }
+
+ /** Returns the list of methods of this class, in order of the object's dispatch table. */
+ public List getMethods() {
+ return methods;
+ }
+}
diff --git a/src/main/java/chocopy/common/codegen/CodeGenBase.java b/src/main/java/chocopy/common/codegen/CodeGenBase.java
new file mode 100644
index 0000000..cd82097
--- /dev/null
+++ b/src/main/java/chocopy/common/codegen/CodeGenBase.java
@@ -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.
+ *
+ *
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.
+ *
+ *
This class also implements logic to emit global variables, object prototypes and dispatch
+ * tables, as well as int/str/bool constants.
+ *
+ *
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.
+ *
+ *
All non-public members of this class are `protected`, and can be overridden by sub-classes to
+ * extend change functionality.
+ *
+ *
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 globalVars = new ArrayList<>();
+
+ /**
+ * A list of program classes, whose prototype objects and dispatch tables are emitted in the
+ * backend.
+ */
+ protected final List classes = new ArrayList<>();
+
+ /**
+ * A list of functions (including methods and nested functions) whose bodies are emitted in the
+ * backend.
+ */
+ protected final List 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 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.
+ *
+ *
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.
+ *
+ *
This label is guaranteed to be unique amongst labels generated by invoking this method.
+ * All such labels have a prefix of `label_`.
+ *
+ *
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.
+ *
+ *
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.
+ *
+ *
PARENTFUNCINFO is a descriptor of the enclosing function and is null for global functions
+ * and methods.
+ *
+ *
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).
+ *
+ *
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 parentSymbolTable,
+ FuncInfo parentFuncInfo,
+ Consumer 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).
+ *
+ *
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.
+ *
+ *
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.
+ *
+ *
These variables are allocated on the stack in activation frames.
+ *
+ *
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).
+ *
+ *
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.
+ *
+ *
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 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 {
+ /** 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 {
+ /** 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 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 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 statements);
+
+ /** Emits code for the body of user-defined function FUNCINFO. */
+ protected abstract void emitUserDefinedFunction(FuncInfo funcInfo);
+
+ /**
+ * Emits code outside the ChocoPy program.
+ *
+ *
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();
+ }
+}
diff --git a/src/main/java/chocopy/common/codegen/Constants.java b/src/main/java/chocopy/common/codegen/Constants.java
new file mode 100644
index 0000000..e0b80f4
--- /dev/null
+++ b/src/main/java/chocopy/common/codegen/Constants.java
@@ -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.
+ *
+ *
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 intConstants = new HashMap<>();
+
+ /** A cache for string-valued constants. */
+ final Map 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.
+ *
+ *
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;
+ }
+ }
+}
diff --git a/src/main/java/chocopy/common/codegen/FuncInfo.java b/src/main/java/chocopy/common/codegen/FuncInfo.java
new file mode 100644
index 0000000..cdcb4f1
--- /dev/null
+++ b/src/main/java/chocopy/common/codegen/FuncInfo.java
@@ -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.
+ *
+ *
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.
+ *
+ *
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.
+ *
+ *
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 params = new ArrayList<>();
+
+ /** A list of local variable descriptors. */
+ protected final List locals = new ArrayList<>();
+
+ /** The function body. */
+ protected final List statements = new ArrayList<>();
+
+ /** The local symbol table that binds identifiers seen in the function's body. */
+ protected final SymbolTable 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.
+ *
+ *
The method should accept one parameter of type `FuncInfo`.
+ */
+ protected final Consumer 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 parentSymbolTable,
+ FuncInfo parentFuncInfo,
+ Consumer 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 stmts) {
+ statements.addAll(stmts);
+ }
+
+ /**
+ * Returns the index of parameter or local variable NAME in the function's activation record.
+ *
+ *
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.
+ *
+ *
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 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.
+ *
+ *
This list is mainly used in generating code for initializing local variables that are not
+ * parameters.
+ */
+ public List getLocals() {
+ return locals;
+ }
+
+ /** Returns the list of statements in the function's body. */
+ public List getStatements() {
+ return statements;
+ }
+
+ /**
+ * Returns the function's local symbol table.
+ *
+ * @return the function's local symbol table
+ */
+ public SymbolTable 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);
+ }
+}
diff --git a/src/main/java/chocopy/common/codegen/GlobalVarInfo.java b/src/main/java/chocopy/common/codegen/GlobalVarInfo.java
new file mode 100644
index 0000000..15da167
--- /dev/null
+++ b/src/main/java/chocopy/common/codegen/GlobalVarInfo.java
@@ -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;
+ }
+}
diff --git a/src/main/java/chocopy/common/codegen/Label.java b/src/main/java/chocopy/common/codegen/Label.java
new file mode 100644
index 0000000..fdb9ffc
--- /dev/null
+++ b/src/main/java/chocopy/common/codegen/Label.java
@@ -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;
+ }
+}
diff --git a/src/main/java/chocopy/common/codegen/RiscVBackend.java b/src/main/java/chocopy/common/codegen/RiscVBackend.java
new file mode 100644
index 0000000..d1c512c
--- /dev/null
+++ b/src/main/java/chocopy/common/codegen/RiscVBackend.java
@@ -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.
+ *
+ *
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);
+ }
+}
diff --git a/src/main/java/chocopy/common/codegen/StackVarInfo.java b/src/main/java/chocopy/common/codegen/StackVarInfo.java
new file mode 100644
index 0000000..609d195
--- /dev/null
+++ b/src/main/java/chocopy/common/codegen/StackVarInfo.java
@@ -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;
+ }
+}
diff --git a/src/main/java/chocopy/common/codegen/SymbolInfo.java b/src/main/java/chocopy/common/codegen/SymbolInfo.java
new file mode 100644
index 0000000..d7a864d
--- /dev/null
+++ b/src/main/java/chocopy/common/codegen/SymbolInfo.java
@@ -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 {}
diff --git a/src/main/java/chocopy/common/codegen/VarInfo.java b/src/main/java/chocopy/common/codegen/VarInfo.java
new file mode 100644
index 0000000..d213bcd
--- /dev/null
+++ b/src/main/java/chocopy/common/codegen/VarInfo.java
@@ -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;
+ }
+}
diff --git a/src/main/java/chocopy/pa3/CodeGenImpl.java b/src/main/java/chocopy/pa3/CodeGenImpl.java
new file mode 100644
index 0000000..47a31c8
--- /dev/null
+++ b/src/main/java/chocopy/pa3/CodeGenImpl.java
@@ -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.
+ *
+ *
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.
+ *
+ *
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.
+ *
+ *
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.
+ *
+ *
You only need to generate code for statements.
+ *
+ * @param statements top level statements
+ */
+ protected void emitTopLevel(List 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.
+ *
+ *
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 {
+ /*
+ * 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 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.
+ *
+ *
This method is called after emitting the top level and the function bodies for each
+ * function.
+ *
+ *
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.
+ *
+ *
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.
+ *
+ *