A collection of functions for transforming the ANTLR AST into a more desirable AST, as well as functions to operate on the transformed AST. | (ns mini-java.ast
(:require [clojure.reflect :refer [typename]]
[mini-java.util :as util])
(:import [org.antlr.v4.runtime.tree TerminalNodeImpl]
[mini_java.antlr MiniJavaParser])) |
Returns the line and column of the first token in a given node. | (defn- node-line-and-column
[node]
(let [token (.getStart node)]
(util/token-line-and-column token))) |
Attaches line, column, and context metadata to the given object. | (defn- with-line-and-column
[node ctx obj]
(let [[line column] (node-line-and-column node)]
(with-meta obj
{:line line
:column column
:context ctx}))) |
Returns the context metadata from the given node. | (defn context [node] (-> node meta :context)) |
An array of all inner classes of MiniJavaParser. | (def ^:private parser-inner-classes (.getClasses MiniJavaParser)) |
Transform the given type generated by ANTLR into a clojure keyword. For example, if given the type ClassDeclarationContext, outputs :class-declaration. | (defn- typeify
[type]
(let [str-name (-> type
typename
(clojure.string/replace #".*MiniJavaParser\$" "")
(clojure.string/replace #"Context" "")
util/camel->lisp)
kw-name (keyword str-name)]
[kw-name type])) |
A mapping from MiniJavaParser inner class types to their keyword representations. TerminalNodeImpl -> :terminal-node is added manually, as it is the only type not ending with "Context" which needs to be used. | (def ^:private type->key
(assoc (into {} (map (comp vec reverse typeify) parser-inner-classes))
TerminalNodeImpl :terminal-node)) |
A mapping from objects to their type keywords, as given by type->key. | (def ^:private obj->type-key (comp type->key type)) |
Returns all children of a given node. | (defn- children [node] (map #(.getChild node %) (range (.getChildCount node)))) |
Removes the outer braces from a node. | (defn- remove-braces [nodes] (-> nodes rest butlast)) |
(defn- var-declaration? [x]
(= :var-declaration
(context x))) | |
(defn- field-declaration? [x]
(= :field-declaration
(context x))) | |
(defn- method-declaration? [x]
(= :method-declaration
(context x))) | |
Multimethod for transforming an ANTLR TreeNode into a minimal hash-map representation. Dispatches on the keyword representation of the node's type. Any transformed node which implements IObj will have attached metadata containing the line, column, and context of the node. | (defmulti ast obj->type-key) |
(defmethod ast :default [node]
"Unknown node type reached. If this happens then a bug occurred. Crash."
(throw (ex-info "Unknown node type"
{:type :unknown-node-type,
:node node}))) | |
(defmethod ast :terminal-node [node] "Reached a terminal node, simply transform it into the underlying text of its symbol." (-> node .-symbol .getText)) | |
(defmethod ast :goal [node]
"The root of any valid parse tree. Transform into a hash map containing
the transformed main and other classes."
(let [children (children node)
main-class (first children)
classes (-> children rest butlast)]
(with-line-and-column node :goal
{:main (ast main-class),
:classes (map ast classes)}))) | |
(defmethod ast :main-class-declaration [node]
"Transform a main class declaration into a hash-map containing its name
and body statement."
(with-line-and-column node :main-class-declaration
{:name (ast (.getChild node 1)),
:body (ast (.getChild node 2))})) | |
(defmethod ast :class-declaration [node]
"Transform a non-main class declaration into a hash-map containing its
name, its parent's name, its variables, and methods."
(let [child? (= 5 (.getChildCount node))
;; determine the index of the body, which depends on whether it
;; has a parent
body-idx (if child? 4 2)
;; parsing the body yields a hash-map of fields and methods
{:keys [vars methods]} (ast (.getChild node body-idx))]
(with-line-and-column node :class-declaration
{:name (ast (.getChild node 1)),
:parent (when child? (ast (.getChild node 3))),
:vars vars
:methods methods}))) | |
(defmethod ast :main-class-body [node] "Transform a main class body, which just results in the transformation of its only method." (ast (.getChild node 1))) | |
(defmethod ast :main-method [node] "Transform the main method, which just results in the transformation of its only statement." (ast (.getChild node 2))) | |
(defmethod ast :class-body [node]
"Transform a non-main class body, which results in a hash-map containing its
transformed variables and methods."
(let [children (children node)
;; transform all declarations within the class, which results in a
;; seq of both variable and method declarations, which are separated
;; here based on their context metadata
declarations (map ast (remove-braces children))
vars (filter field-declaration? declarations)
methods (filter method-declaration? declarations)]
(with-line-and-column node :class-body
{:vars vars,
:methods methods}))) | |
(defmethod ast :method-declaration [node]
"Transform a method, resulting in a hash-map containing its name,
return type, arguments, local variables, and body statements,
all transformed.."
(let [;; transforming the body of a method results in a hash-map separating
;; the variable declarations and actual statements of the method
{:keys [vars body]} (ast (.getChild node 4))]
(with-line-and-column node :method-declaration
{:name (ast (.getChild node 2)),
:type (ast (.getChild node 1)),
:args (ast (.getChild node 3)),
:vars vars,
:body body}))) | |
(defmethod ast :method-body [node]
"Transforms a method body, separating its variable declarations and body
statements into a hash-map. No context metadata is preserved, as it is
destructured into the method declaration context."
(let [children (remove-braces (children node))
body-nodes (map ast children)]
{:vars (filter var-declaration? body-nodes)
:body (filter (comp not var-declaration?) body-nodes)})) | |
(defmethod ast :field-declaration [node]
"Transforms a field declaration into a hash-map containing both its
name and its type."
(with-line-and-column node :field-declaration
{:name (ast (.getChild node 1)),
:type (ast (.getChild node 0))})) | |
(defmethod ast :var-declaration [node]
"Transforms a variable declaration into a hash-map containing both its
name and its type."
(with-line-and-column node :var-declaration
{:name (ast (.getChild node 1)),
:type (ast (.getChild node 0))})) | |
(defmethod ast :nested-statement [node]
"Transforms a nested statement into a seq of the statements it contains."
(with-line-and-column node :nested-statement
(->> node
children
remove-braces
(map ast)))) | |
(defmethod ast :if-else-statement [node]
"Transforms an if/else statement into a hash-map containing the predicate,
then, and else parts."
(with-line-and-column node :if-else-statement
{:pred (ast (.getChild node 2)),
:then (ast (.getChild node 4)),
:else (ast (.getChild node 6))})) | |
(defmethod ast :while-statement [node]
"Transforms a while statement into a hash-map containing the predicate and
body statement."
(with-line-and-column node :while-statement
{:pred (ast (.getChild node 2)),
:body (ast (.getChild node 4))})) | |
(defmethod ast :print-statement [node]
"Transforms a print statement into a hash-map containing only its single
argument."
(with-line-and-column node :print-statement
{:arg (ast (.getChild node 2))})) | |
(defmethod ast :assign-statement [node]
"Transforms an assignment statement into a hash-map containing the name
of its target and the source expression."
(with-line-and-column node :assign-statement
{:target (ast (.getChild node 0)),
:source (ast (.getChild node 2))})) | |
(defmethod ast :array-assign-statement [node]
"Transforms an array assignment statement into a hash-map containing the
name of its target and the source expression."
(with-line-and-column node :array-assign-statement
{:target (ast (.getChild node 0)),
:index (ast (.getChild node 2)),
:source (ast (.getChild node 5))})) | |
(defmethod ast :return-statement [node]
"Transforms a return statement into a hash-map containing only its return
value."
(with-line-and-column node :return-statement
{:return-value (ast (.getChild node 1))})) | |
(defmethod ast :recur-statement [node]
"Transforms a recur statement into a hash-map containing its predicate,
argument list, and base-case."
(with-line-and-column node :recur-statement
{:pred (ast (.getChild node 1)),
:args (ast (.getChild node 3)),
:base (ast (.getChild node 5))})) | |
(defmethod ast :method-argument-list [node]
"Transforms a method argument list into a seq of the arguments being passed."
(let [children (children node)
args (take-nth 2 (-> children rest butlast))]
(with-line-and-column node :method-argument-list
(map ast args)))) | |
(defmethod ast :formal-parameters [node]
"Transforms formal parameters into either an empty seq, or a list of formal
parameters."
(with-line-and-column node :formal-parameters
(let [length (.getChildCount node)]
(if (= 3 length)
(ast (.getChild node 1))
())))) | |
(defmethod ast :formal-parameter-list [node]
"Transforms a non-empty formal parameter list into a seq of its formal
parameters. Each argument is assigned a sequential argument index, starting
from 0."
(->> node
children
(take-nth 2) ; ignore commas
(map ast)
(map (fn [i arg] (assoc arg :arg-index i))
(range)))) | |
(defmethod ast :formal-parameter [node]
"Transforms a formal parameter into a hash-map containing its type and name."
(with-line-and-column node :formal-parameter
{:type (ast (.getChild node 0)),
:name (ast (.getChild node 1))})) | |
(defmethod ast :type [node] "Transforms a type into its underlying representation." (ast (.getChild node 0))) | |
(defn- unary-expression [node]
"Transforms a unary expression into a hash-map containing its operand."
(with-line-and-column node (obj->type-key node)
{:operand (ast (.getChild node 1))})) | |
(defn- binary-expression [node]
"Transforms a binary expression into a hash-map containing its operands."
(with-line-and-column node (obj->type-key node)
{:left (ast (.getChild node 0)),
:right (ast (.getChild node 2))})) | |
(defmethod ast :and-expression [node] (binary-expression node)) | |
(defmethod ast :lt-expression [node] (binary-expression node)) | |
(defmethod ast :add-expression [node] (binary-expression node)) | |
(defmethod ast :sub-expression [node] (binary-expression node)) | |
(defmethod ast :mul-expression [node] (binary-expression node)) | |
(defmethod ast :array-access-expression [node]
"Transforms an array access expression into a hash-map containing an
expression which evaluates to an array, and the index being accessed."
(with-line-and-column node :array-access-expression
{:array (ast (.getChild node 0)),
:index (ast (.getChild node 2))})) | |
(defmethod ast :array-length-expression [node]
"Transforms an array length expression into a hash-map containing only the
expression which evaluates to the array."
(with-line-and-column node :array-length-expression
{:array (ast (.getChild node 0))})) | |
(defmethod ast :method-call-expression [node]
"Transforms a method call expression into a hash-map containing the
expression it is being called on, the name of the method, and the arguments
it is being called with."
(with-line-and-column node :method-call-expression
{:caller (ast (.getChild node 0)),
:method (ast (.getChild node 2)),
:args (ast (.getChild node 3))})) | |
(defmethod ast :int-lit-expression [node]
"Transforms an integer literal expression into a hash-map containing only
its value as an Integer object."
(with-line-and-column node :int-lit-expression
{:value (-> node
(.getChild 0)
ast
Integer.)})) | |
(defmethod ast :boolean-lit-expression [node]
"Transforms a boolean literal expression into a hash-map containing only
its value as a Boolean object."
(with-line-and-column node :boolean-lit-expression
{:value (-> node
(.getChild 0)
ast
Boolean.)})) | |
(defmethod ast :identifier-expression [node]
"Transforms an identitifier expression into a hash-map containing only
the string representation of the ID."
(with-line-and-column node :identifier-expression
{:id (ast (.getChild node 0))})) | |
(defmethod ast :this-expression [node] "Transforms a this expression into just the :this keyword." :this) | |
(defmethod ast :array-instantiation-expression [node]
"Transforms an array instantiation expression into a hash-map containing only
its size. Arrays can only be int arrays, so there is no need to specify the
type."
(with-line-and-column node :array-instantiation-expression
{:size (ast (.getChild node 3))})) | |
(defmethod ast :object-instantiation-expression [node]
"Transforms an object instantiation expression into a hash-map containing
only its type. No constructor parameters need to be preserved, as MiniJava
constructors take no arguments."
(with-line-and-column node :object-instantiation-expression
{:type (ast (.getChild node 1))})) | |
(defmethod ast :not-expression [node] (unary-expression node)) | |
(defmethod ast :neg-expression [node] (unary-expression node)) | |
(defmethod ast :paren-expression [node] "Transforms a parenthesis expression into whatever is contained within it. Parentheses are only important at parse-time." (ast (.getChild node 1))) | |
(defmethod ast :int-type [node] "Transforms an int type expression into the keyword :int." :int) | |
(defmethod ast :int-array-type [node] "Transforms an int array type expression into the keyword :int<>." :int<>) | |
(defmethod ast :boolean-type [node] "Transforms a boolean type expression into the keyword :boolean." :boolean) | |