aboutsummaryrefslogtreecommitdiff
path: root/src/cljcc
diff options
context:
space:
mode:
authorShagun Agrawal <agrawalshagun07@gmail.com>2024-11-07 01:53:11 +0530
committerShagun Agrawal <agrawalshagun07@gmail.com>2024-11-07 01:53:11 +0530
commit206ebed329abcecbb46418ad2cec2afe7386e1d1 (patch)
tree7bea7aae32613fff0827068d76aa6e70a7a62e1d /src/cljcc
parenta9e828cc6aeab1400217d2af9fa20c93b2183baa (diff)
Add resolving and typechecking for storage classes
Diffstat (limited to 'src/cljcc')
-rw-r--r--src/cljcc/analyzer.clj594
-rw-r--r--src/cljcc/exception.clj3
-rw-r--r--src/cljcc/parser.clj5
3 files changed, 404 insertions, 198 deletions
diff --git a/src/cljcc/analyzer.clj b/src/cljcc/analyzer.clj
index 8eb0d42..d278ef4 100644
--- a/src/cljcc/analyzer.clj
+++ b/src/cljcc/analyzer.clj
@@ -2,7 +2,8 @@
(:require [cljcc.lexer :as l]
[clojure.pprint :as pp]
[cljcc.util :as u]
- [cljcc.parser :as p]))
+ [cljcc.parser :as p]
+ [cljcc.exception :as exc]))
(defn- unique-identifier
([] (unique-identifier "analyzer"))
@@ -11,179 +12,258 @@
(defn- copy-identifier-map
"Returns a copy of the identifier map.
- Sets :from-current-block as false for every entry. Used when going into a inner scope."
- [identifier-map]
- (zipmap (keys identifier-map)
- (map (fn [m]
- (update m :from-current-block (fn [_] false)))
- (vals identifier-map))))
+ Sets :at-top-level false, as it's going inside a scope. ( Could be fn definition, compound statement ).
+ Sets :from-current-block as false for every symbol. Used when going into a inner scope."
+ [ident->symbol]
+ (let [set-from-current-block-as-false (fn [i->s]
+ (zipmap (keys i->s)
+ (map (fn [s]
+ (assoc s :from-current-block false))
+ (vals i->s))))]
+ (-> ident->symbol
+ (dissoc :at-top-level)
+ set-from-current-block-as-false
+ (assoc :at-top-level false))))
(declare resolve-block)
-(defn- resolve-exp [e identifier-map]
+(defn- resolve-exp [e ident->symbol]
(condp = (:exp-type e)
:constant-exp e
- :variable-exp (if (contains? identifier-map (:identifier e))
- (p/variable-exp-node (:name (get identifier-map (:identifier e))))
- (throw (ex-info "Undeclared variable seen." {:variable e})))
+ :variable-exp (if (contains? ident->symbol (:identifier e))
+ (p/variable-exp-node (:name (get ident->symbol (:identifier e))))
+ (exc/analyzer-error "Undeclared variable seen." {:variable e}))
:assignment-exp (let [left (:left e)
right (:right e)
op (:assignment-operator e)
left-var? (= :variable-exp (:exp-type left))]
(if left-var?
- (p/assignment-exp-node (resolve-exp left identifier-map)
- (resolve-exp right identifier-map)
+ (p/assignment-exp-node (resolve-exp left ident->symbol)
+ (resolve-exp right ident->symbol)
op)
- (throw (ex-info "Invalid lvalue." {:lvalue e}))))
- :binary-exp (p/binary-exp-node (resolve-exp (:left e) identifier-map)
- (resolve-exp (:right e) identifier-map)
+ (exc/analyzer-error "Invalid lvalue in assignment expression." {:lvalue e})))
+ :binary-exp (p/binary-exp-node (resolve-exp (:left e) ident->symbol)
+ (resolve-exp (:right e) ident->symbol)
(:binary-operator e))
- :unary-exp (p/unary-exp-node (:unary-operator e) (resolve-exp (:value e) identifier-map))
- :conditional-exp (p/conditional-exp-node (resolve-exp (:left e) identifier-map)
- (resolve-exp (:middle e) identifier-map)
- (resolve-exp (:right e) identifier-map))
+ :unary-exp (p/unary-exp-node (:unary-operator e) (resolve-exp (:value e) ident->symbol))
+ :conditional-exp (p/conditional-exp-node (resolve-exp (:left e) ident->symbol)
+ (resolve-exp (:middle e) ident->symbol)
+ (resolve-exp (:right e) ident->symbol))
:function-call-exp (let [fn-name (:identifier e)
args (:arguments e)]
- (if (contains? identifier-map fn-name)
- (p/function-call-exp-node (:new-name (get identifier-map fn-name))
- (map #(resolve-exp % identifier-map) args))
+ (if (contains? ident->symbol fn-name)
+ (p/function-call-exp-node (:new-name (get ident->symbol fn-name))
+ (map #(resolve-exp % ident->symbol) args))
(throw (ex-info "Undeclared function !" {:function-name fn-name}))))
- (throw (ex-info "Analyzer error. Invalid expression type" {:exp e}))))
+ (exc/analyzer-error "Invalid expression." {:exp e})))
(defn- resolve-optional-exp [e identifier-map]
(if (nil? e)
e
(resolve-exp e identifier-map)))
+(defn- resolve-file-scope-variable-declaration
+ "Adds file scope variable declaration to scope.
+
+ Directly adds variable declaration to map as it is top level."
+ [{:keys [identifier] :as declaration} ident->symbol]
+ {:declaration declaration
+ :ident->symbol (assoc ident->symbol identifier {:new-name identifier
+ :name identifier
+ :from-current-scope true
+ :has-linkage true})})
+
+(defn- resolve-local-variable-declaration
+ "Add local variable declaration.
+
+ Validates for variables declared with same name.
+ Validates for variables declared from different scope, but with conflicting storage class."
+ [{:keys [identifier initial storage-class] :as declaration} ident->symbol]
+ (let [prev-entry (get ident->symbol identifier)
+ extern? (= storage-class :extern)
+ _ (when (and prev-entry (:from-current-scope prev-entry))
+ (when (not (and (:has-linkage prev-entry) extern?))
+ (exc/analyzer-error "Conflicting local declaration." {:declaration declaration})))]
+ (if extern?
+ {:declaration declaration
+ :ident->symbol (assoc ident->symbol identifier {:new-name identifier
+ :name identifier
+ :from-current-scope true
+ :has-linkage true})}
+ (let [unique-name (unique-identifier identifier)
+ updated-symbols (assoc ident->symbol identifier {:new-name unique-name
+ :name unique-name
+ :from-current-scope true
+ :has-linkage false})
+ init-value (when initial (resolve-exp initial updated-symbols))]
+ {:declaration (p/variable-declaration-node unique-name storage-class init-value)
+ :ident->symbol updated-symbols}))))
+
(defn- resolve-variable-declaration
"Resolves variable declarations.
Ensures variable not declared twice in the current scope."
- [{:keys [identifier initial] :as d} identifier-map]
- (if (and (contains? identifier-map identifier)
- (:from-current-block (get identifier-map identifier)))
- (throw (ex-info "Analyzer error. Duplicate variable declaration." {:declaration d}))
- (let [unique-name (unique-identifier identifier)
- updated-identifier-map (assoc identifier-map identifier {:name unique-name
- :from-current-block true
- :has-linkage false})
- init-value (when initial (resolve-exp initial updated-identifier-map))]
- {:declaration (p/variable-declaration-node unique-name init-value)
- :identifier-map updated-identifier-map})))
-
-(defn- resolve-parameter [{:keys [identifier] :as param} identifier-map]
- (if (and (contains? identifier-map identifier)
- (:from-current-block (get identifier-map identifier)))
- (throw (ex-info "Analyzer error. Parameter name duplicated." {:parameter param}))
+ [decl {:keys [at-top-level] :as ident->symbol}]
+ (if at-top-level
+ (resolve-file-scope-variable-declaration decl ident->symbol)
+ (resolve-local-variable-declaration decl ident->symbol)))
+
+(defn- resolve-parameter [{:keys [identifier] :as param} ident->symbol]
+ (if (and (contains? ident->symbol identifier)
+ (:from-current-block (get ident->symbol identifier)))
+ (exc/analyzer-error "Parameter name duplicated." {:parameter param})
(let [unique-name (unique-identifier identifier)
- updated-identifier-map (assoc identifier-map identifier {:name unique-name
- :from-current-block true
- :has-linkage false})]
- {:parameter (p/variable-declaration-node unique-name)
- :identifier-map updated-identifier-map})))
-
-(defn- resolve-parameters [params identifier-map]
+ storage-class nil
+ updated-identifier-map (assoc ident->symbol identifier {:name unique-name
+ :from-current-block true
+ :has-linkage false})]
+ {:parameter (p/variable-declaration-node unique-name storage-class)
+ :ident->symbol updated-identifier-map})))
+
+(defn- resolve-parameters [params ident->symbol]
(reduce (fn [acc p]
- (let [{:keys [parameter identifier-map]} (resolve-parameter p (:identifier-map acc))]
+ (let [{:keys [parameter ident->symbol]} (resolve-parameter p (:ident->symbol acc))]
{:parameters (conj (:parameters acc) parameter)
- :identifier-map identifier-map}))
- {:parameters [] :identifier-map identifier-map}
+ :ident->symbol ident->symbol}))
+ {:parameters [] :ident->symbol ident->symbol}
params))
(defn- resolve-function-declaration
"Resolve function declaration.
Ensures functions not declared twice in current scope with incorrect linkage."
- [{:keys [identifier parameters return-type body] :as d} identifier-map]
- (let [prev-entry (get identifier-map identifier)
- already-declared-var? (and (contains? identifier-map identifier)
- (:from-current-block (get identifier-map identifier))
+ [{:keys [identifier storage-class parameters return-type body] :as d} ident->symbol]
+ (let [prev-entry (get ident->symbol identifier)
+ already-declared-var? (and (contains? ident->symbol identifier)
+ (:from-current-block (get ident->symbol identifier))
(not (:has-linkage prev-entry)))
- illegally-redeclared? (and (contains? identifier-map identifier)
+ illegally-redeclared? (and (contains? ident->symbol identifier)
(:from-current-scope prev-entry)
(not (:has-linkage prev-entry)))
- inside-function-definition? (:inside-function-definition (:inside-inner-scope identifier-map))
+ static? (= :static storage-class)
+ inside-function-definition? (not (:at-top-level ident->symbol))
_ (when already-declared-var?
- (throw (ex-info "Analyzer Error. Variable already declared in same scope." {:declaration d})))
+ (exc/analyzer-error "Variable already declared in same scope." {:declaration d}))
_ (when illegally-redeclared?
- (throw (ex-info "Analyzer Error. Function duplicate declaration." {:declaration d})))
- updated-identifier-map (assoc identifier-map identifier {:new-name identifier
- :name identifier
- :from-current-block true
- :from-current-scope true
- :has-linkage true})
+ (exc/analyzer-error "Function duplicate declaration." {:declaration d}))
+ updated-identifier-map (assoc ident->symbol identifier {:new-name identifier
+ :name identifier
+ :from-current-block true
+ :from-current-scope true
+ :has-linkage true})
inner-map (copy-identifier-map updated-identifier-map)
- {new-params :parameters, inner-map :identifier-map} (resolve-parameters parameters inner-map)
+ {new-params :parameters, inner-map :ident->symbol} (resolve-parameters parameters inner-map)
_ (when (and body inside-function-definition?)
- (throw (ex-info "Analyzer Error. Nested function definition not allowed." {:declaration d})))
- new-body (when body (resolve-block body (assoc inner-map :inside-inner-scope {:inside-function-definition true})))]
- {:declaration (p/function-declaration-node return-type identifier new-params (:block new-body))
- :identifier-map updated-identifier-map}))
-
-(defn- resolve-declaration [{:keys [declaration-type] :as d} identifier-map]
+ (exc/analyzer-error "Nested function definition not allowed." {:declaration d
+ :ident->symbol ident->symbol}))
+ _ (when (and inside-function-definition? static?)
+ (exc/analyzer-error "Nested static function declarations cannot exist." {:declaration d}))
+ new-body (when body (resolve-block body inner-map))]
+ {:declaration (p/function-declaration-node return-type storage-class identifier new-params (:block new-body))
+ :ident->symbol updated-identifier-map}))
+
+(defn- resolve-declaration [{:keys [declaration-type] :as d} ident->symbol]
(condp = declaration-type
- :variable (resolve-variable-declaration d identifier-map)
- :function (resolve-function-declaration d identifier-map)
+ :variable (resolve-variable-declaration d ident->symbol)
+ :function (resolve-function-declaration d ident->symbol)
(throw (ex-info "Analyzer Error. Invalid declaration type." {:declaration d}))))
-(defn- resolve-for-init [for-init var-mp]
+(defn- resolve-for-init [for-init ident->symbol]
(if (= (:type for-init) :declaration)
- (resolve-declaration for-init var-mp)
- (resolve-optional-exp for-init var-mp)))
-
-(defn- resolve-statement [s mp]
- (condp = (:statement-type s)
- :return (p/return-statement-node (resolve-exp (:value s) mp))
- :expression (p/expression-statement-node (resolve-exp (:value s) mp))
- :if (if (:else-statement s)
- (p/if-statement-node (resolve-exp (:condition s) mp)
- (resolve-statement (:then-statement s) mp)
- (resolve-statement (:else-statement s) mp))
- (p/if-statement-node (resolve-exp (:condition s) mp)
- (resolve-statement (:then-statement s) mp)))
- :break s
- :continue s
- :while (p/while-statement-node (resolve-exp (:condition s) mp)
- (resolve-statement (:body s) mp))
- :do-while (p/do-while-statement-node (resolve-exp (:condition s) mp)
- (resolve-statement (:body s) mp))
- :for (let [new-identifier-map (copy-identifier-map mp)
- for-init (resolve-for-init (:init s) new-identifier-map)
- new-var-map (if (:declaration for-init)
- (:identifier-map for-init)
- new-identifier-map) ; updates new-identifier-map so that include possible
- ; variable declaration
- for-init (if (:declaration for-init)
- (:declaration for-init)
- for-init)
- condition (resolve-optional-exp (:condition s) new-var-map)
- post (resolve-optional-exp (:post s) new-var-map)
- body (resolve-statement (:body s) new-var-map)]
- (p/for-statement-node for-init condition post body))
- :compound (let [updated-mp (copy-identifier-map mp)]
- (p/compound-statement-node (:block (resolve-block (:block s) updated-mp))))
- :empty (p/empty-statement-node)
- (throw (ex-info "Analyzer error. Invalid statement." {:statement s}))))
-
-(defn- resolve-block-item [{:keys [type] :as item} identifier-map]
+ (resolve-declaration for-init ident->symbol)
+ (resolve-optional-exp for-init ident->symbol)))
+
+(defmulti resolve-statement
+ "Resolves statements in a given scope.
+
+ Scope here refers to the ident->symbol map, which holds declarations
+ visisble to statement at this time.
+
+ Dispatches based on the type of statement.
+
+ Returns statement after recursively resolving all expressions and statements.
+ "
+ (fn [statement _ident->symbol]
+ (:statement-type statement)))
+
+(defmethod resolve-statement :default [statement _]
+ (exc/analyzer-error "Invalid statement." {:statement statement}))
+
+(defmethod resolve-statement :return [{:keys [value]} ident->symbol]
+ (p/return-statement-node (resolve-exp value ident->symbol)))
+
+(defmethod resolve-statement :break [statement _]
+ statement)
+
+(defmethod resolve-statement :continue [statement _]
+ statement)
+
+(defmethod resolve-statement :empty [statement _]
+ statement)
+
+(defmethod resolve-statement :expression [{:keys [value]} ident->symbol]
+ (p/expression-statement-node (resolve-exp value ident->symbol)))
+
+(defmethod resolve-statement :if [{:keys [condition then-statement else-statement]} ident->symbol]
+ (if else-statement
+ (p/if-statement-node (resolve-exp condition ident->symbol)
+ (resolve-statement then-statement ident->symbol)
+ (resolve-statement else-statement ident->symbol))
+ (p/if-statement-node (resolve-exp condition ident->symbol)
+ (resolve-statement then-statement ident->symbol))))
+
+(defmethod resolve-statement :while [{:keys [condition body]} ident->symbol]
+ (p/while-statement-node (resolve-exp condition ident->symbol)
+ (resolve-statement body ident->symbol)))
+
+(defmethod resolve-statement :do-while [{:keys [condition body]} ident->symbol]
+ (p/do-while-statement-node (resolve-exp condition ident->symbol)
+ (resolve-statement body ident->symbol)))
+
+(defmethod resolve-statement :for [{:keys [init condition post body]} ident->symbol]
+ (let [for-scope-identifier-map (copy-identifier-map ident->symbol)
+ resolved-for-init (resolve-for-init init for-scope-identifier-map)
+ for-scope-identifier-map (if (:declaration resolved-for-init) ; updates symbol map if for initializer is declaration
+ (:ident->symbol resolved-for-init)
+ for-scope-identifier-map)
+ resolved-for-init (if (:declaration resolved-for-init) ; getting the underlying declaration, if it is
+ (:declaration resolved-for-init)
+ resolved-for-init)
+ condition (resolve-optional-exp condition for-scope-identifier-map)
+ post (resolve-optional-exp post for-scope-identifier-map)
+ body (resolve-statement body for-scope-identifier-map)]
+ (p/for-statement-node resolved-for-init condition post body)))
+
+(defmethod resolve-statement :compound [{:keys [block]} ident->symbol]
+ (p/compound-statement-node (:block (resolve-block block (copy-identifier-map ident->symbol)))))
+
+(defn- resolve-block-item [{:keys [type] :as item} ident->symbol]
(condp = type
- :declaration (let [v (resolve-declaration item identifier-map)]
+ :declaration (let [v (resolve-declaration item ident->symbol)]
{:block-item (:declaration v)
- :identifier-map (:identifier-map v)})
- :statement {:block-item (resolve-statement item identifier-map)
- :identifier-map identifier-map}
- (throw (ex-info "Analyzer Error. Invalid statement/declaration." {item item}))))
+ :ident->symbol (:ident->symbol v)})
+ :statement {:block-item (resolve-statement item ident->symbol)
+ :ident->symbol ident->symbol}
+ (exc/analyzer-error "Invalid statement/declaration type." item)))
(defn- resolve-block
+ "Resolves a block with a given symbol table.
+
+ ident->symbol holds identifier to symbol mapping.
+ Symbol contains the type information, generated variable name etc.
+
+ | key | description |
+ |----------------|-------------|
+ |`:at-top-level` | Is current level top or not ( default true)|"
([block]
- (resolve-block block {}))
- ([block identifier-map]
+ (resolve-block block {:at-top-level true}))
+ ([block ident->symbol]
(reduce (fn [acc block-item]
- (let [v (resolve-block-item block-item (:identifier-map acc))]
+ (let [v (resolve-block-item block-item (:ident->symbol acc))]
{:block (conj (:block acc) (:block-item v))
- :identifier-map (:identifier-map v)}))
+ :ident->symbol (:ident->symbol v)}))
{:block []
- :identifier-map identifier-map}
+ :ident->symbol ident->symbol}
block)))
(defn- annotate-label [m l]
@@ -244,88 +324,202 @@
(defn- typecheck-exp
"Returns the expression itself, after typechecking all subexpressions."
- [{:keys [exp-type] :as e} decl-name->symbol]
+ [{:keys [exp-type] :as e} ident->symbol]
(condp = exp-type
:constant-exp e
:variable-exp (let [identifier (:identifier e)
- variable-type (:variable-type (get decl-name->symbol identifier))
- _ (when (not (= :int variable-type))
- (throw (ex-info "Analyzer Error. Function name used as variable." {:exp e})))]
+ var? (= :variable (:type (get ident->symbol identifier)))
+ _ (when (not var?)
+ (exc/analyzer-error "Function name used as variable." {:exp e :ident->symbol ident->symbol}))]
e)
:assignment-exp (do
- (typecheck-exp (:left e) decl-name->symbol)
- (typecheck-exp (:right e) decl-name->symbol)
+ (typecheck-exp (:left e) ident->symbol)
+ (typecheck-exp (:right e) ident->symbol)
e)
:binary-exp (do
- (typecheck-exp (:left e) decl-name->symbol)
- (typecheck-exp (:right e) decl-name->symbol)
+ (typecheck-exp (:left e) ident->symbol)
+ (typecheck-exp (:right e) ident->symbol)
e)
:unary-exp (do
- (typecheck-exp (:value e) decl-name->symbol)
+ (typecheck-exp (:value e) ident->symbol)
e)
:conditional-exp (do
- (typecheck-exp (:left e) decl-name->symbol)
- (typecheck-exp (:right e) decl-name->symbol)
- (typecheck-exp (:middle e) decl-name->symbol)
+ (typecheck-exp (:left e) ident->symbol)
+ (typecheck-exp (:right e) ident->symbol)
+ (typecheck-exp (:middle e) ident->symbol)
e)
- :function-call-exp (let [symbol (decl-name->symbol (:identifier e))
- _ (when (= :int (:variable-type symbol))
+ :function-call-exp (let [symbol (ident->symbol (:identifier e))
+ _ (when (not= :function (:type symbol))
(throw (ex-info "Analyzer Error. Variable used as function name." {:exp e})))
- _ (when (not (= (count (:arguments e)) (:param-count symbol)))
+ _ (when (not= (count (:arguments e)) (:param-count symbol))
(throw (ex-info "Analyzer Error. Function called with the wrong number of arguments." {:exp e})))
- _ (map #(typecheck-exp % decl-name->symbol) (:arguments e))]
+ _ (map #(typecheck-exp % ident->symbol) (:arguments e))]
e)
(throw (ex-info "Analyzer error. Invalid expression type passed to typechecker." {:exp e}))))
-(defn- variable-symbol [variable-type]
- {:variable-type variable-type})
-(defn- function-symbol [param-count defined?]
- {:param-count param-count
- :defined? defined?})
+(defn- fun-attrs [defined? global?]
+ {:type :fun
+ :defined? defined?
+ :global? global?})
+
+(defn- static-attrs [initial-value global?]
+ {:type :static
+ :initial-value initial-value
+ :global? global?})
-(defn- add-parameters [params decl-name->symbol]
- (if (= 0 (count params))
- decl-name->symbol
+(defn- local-attrs []
+ {:type :local})
+
+(defn- variable-symbol [variable-type attrs]
+ {:type :variable
+ :variable-type variable-type
+ :attrs attrs})
+
+(defn- function-symbol [param-count attrs]
+ {:type :function
+ :param-count param-count
+ :attrs attrs})
+
+(defn- add-parameters [params ident->symbol]
+ (if (zero? (count params))
+ ident->symbol
(apply assoc
- decl-name->symbol
- (flatten (map (fn [p] [(:identifier p) (variable-symbol :int)]) params)))))
+ ident->symbol
+ (flatten (map (fn [p] [(:identifier p) (variable-symbol :int (local-attrs))]) params)))))
(declare typecheck-block)
-(defn- typecheck-declaration [{:keys [declaration-type identifier] :as d} decl-name->symbol]
- (condp = declaration-type
- :variable (let [updated-decl-name->symbol (assoc decl-name->symbol identifier (variable-symbol :int))
- _ (when (:initial d) (typecheck-exp (:initial d) updated-decl-name->symbol))]
- {:declaration d
- :decl-name->symbol updated-decl-name->symbol})
- :function (let [param-count (count (:parameters d))
- has-body? (not (empty? (:body d)))
- previously-declared? (contains? decl-name->symbol identifier)
- _ (when previously-declared?
- (let [old-symbol (get decl-name->symbol identifier)
- _ (when (not= param-count (:param-count old-symbol))
- (throw (ex-info "Analyzer Error. Incompatible function declarations." {:declaration d})))
- _ (when (and (:defined? old-symbol) has-body?)
- (throw (ex-info "Analyzer Error. Function is defined more than once." {:declaration d})))]))
- updated-decl-name->symbol (assoc decl-name->symbol
- identifier
- (function-symbol param-count (or (:defined? (get decl-name->symbol identifier)) has-body?)))]
- (if has-body?
- (let [with-parameters-symbols (add-parameters (:parameters d) updated-decl-name->symbol)
- with-body-symbols (typecheck-block (:body d) with-parameters-symbols)]
- {:declaration d
- :decl-name->symbol (:decl-name->symbol with-body-symbols)})
- {:declaration d
- :decl-name->symbol updated-decl-name->symbol}))
- (throw (ex-info "Analyzer Error. Invalid declaration for typechecker." {:declaration d}))))
+(defn- validate-fn-decl-and-return-updated-attrs
+ [cur-decl old-decl]
+ (let [param-count (count (:parameters cur-decl))
+ old-param-count (:param-count old-decl)
+ has-body? (seq (:body param-count))
+ _ (when (not= param-count old-param-count)
+ (exc/analyzer-error "Incompatible function declarations." {:declaration1 old-decl
+ :declaration2 cur-decl}))
+ defined? (:defined? (:attrs old-decl))
+ _ (when (and defined? has-body?)
+ (exc/analyzer-error "Function is defined more than once." {:declaration cur-decl}))
+ old-global? (:global? (:attrs old-decl))
+ _ (when (and old-global? (= :static (:storage-class cur-decl)))
+ (exc/analyzer-error "Static function definition follows non static." {:declaration cur-decl}))]
+ {:defined? defined?
+ :global? old-global?}))
+
+(defn- typecheck-function-declaration
+ [{:keys [identifier parameters body storage-class] :as decl} ident->symbol]
+ (let [param-count (count parameters)
+ body? (seq body)
+ old-decl (get ident->symbol identifier)
+ {defined? :defined?
+ global? :global?} (if old-decl
+ (validate-fn-decl-and-return-updated-attrs decl old-decl)
+ {:defined? false
+ :global? (not= :static storage-class)})
+ attrs (fun-attrs (or defined? (boolean body?)) global?)
+ updated-symbol-map (assoc ident->symbol identifier
+ (function-symbol param-count attrs))]
+ (if body?
+ (let [with-parameter-symbols (add-parameters parameters updated-symbol-map)
+ with-body-symbols (typecheck-block body (assoc with-parameter-symbols
+ :at-top-level false))]
+ {:declaration decl
+ :ident->symbol (assoc (:ident->symbol with-body-symbols) :at-top-level true)})
+ {:declaration decl
+ :ident->symbol updated-symbol-map})))
+
+(defn- get-initial-value [decl]
+ (cond
+ (= :constant-exp (:exp-type (:initial decl))) {:type :initial
+ :value (:value (:initial decl))}
+ (nil? (:initial decl)) (if (= :extern (:storage-class decl))
+ {:type :no-initializer}
+ {:type :tentative})
+ :else (exc/analyzer-error "Non-constant initializer!" decl)))
+
+(defn- validate-file-scope-decl-return-attrs [cur-decl old-decl]
+ (let [_ (when (not= :variable (:declaration-type old-decl))
+ (exc/analyzer-error "Function redeclared as variable." {:declaration1 old-decl
+ :declaration2 cur-decl}))
+ global? (not= :static (:storage-class cur-decl))
+ global? (cond
+ (= :extern (:storage-class cur-decl)) (:global? (:attrs old-decl))
+ (not= global? (:global? (:attrs old-decl))) (exc/analyzer-error "Conflicting variable linkage." {:declaration1 old-decl
+ :declaration2 cur-decl})
+ :else global?)
+ initial-value (get-initial-value cur-decl)
+ initial-value (cond
+ (=
+ :initial
+ (get-in old-decl [:attrs :initial-value :type])) (if (= (:type initial-value) :initial)
+ (exc/analyzer-error "Conflivting file scope variable definition." {:declarartion1 old-decl
+ :declaration2 cur-decl})
+ (get-in old-decl [:attrs :initial-value]))
+ (and
+ (= :tentative (get-in old-decl [:attrs :initial-value :type]))
+ (not= :initial (:type initial-value))) {:type :tentative}
+ :else initial-value)]
+ {:global? global?
+ :initial-value initial-value}))
+
+(defn- typecheck-file-scope-variable-declaration
+ [{:keys [identifier storage-class ] :as d} ident->symbol]
+ (let [old-decl (get ident->symbol identifier)
+ global? (not= :static storage-class)
+ initial-value (get-initial-value d)
+ {global? :global?
+ initial-value :initial-value} (if old-decl
+ (validate-file-scope-decl-return-attrs d old-decl)
+ {:global? global?
+ :initial-value initial-value})]
+ {:declaration d
+ :ident->symbol (assoc ident->symbol
+ identifier
+ (variable-symbol :int (static-attrs initial-value global?)))}))
+
+(defn- typecheck-local-scope-variable-declaration
+ [{:keys [identifier storage-class initial] :as d} ident->symbol]
+ (cond
+ (= :extern storage-class) (let [_ (when (not (nil? initial))
+ (exc/analyzer-error "Initializer on local extern variable declaration." d))
+ old-decl (get ident->symbol identifier)
+ _ (when (and old-decl (not= :variable (:declaration-type old-decl)))
+ (exc/analyzer-error "Function redeclared as variable." {:declaration1 old-decl
+ :declaration2 d}))]
+ {:declaration d
+ :ident->symbol (assoc ident->symbol
+ identifier
+ (variable-symbol :int (static-attrs {:type :no-initializer} true)))})
+ (= :static storage-class) (let [initial-value (cond
+ (= :constant-exp (:exp-type initial)) {:type :initial
+ :value (:value initial)}
+ (nil? initial) {:type :initial
+ :value 0}
+ :else (exc/analyzer-error "Non-constant initializer on local static variable." d))]
+ {:declaration d
+ :ident->symbol (assoc ident->symbol
+ identifier
+ (variable-symbol :int (static-attrs initial-value false)))})
+ :else (let [updated-symbols (assoc ident->symbol identifier (variable-symbol :int (local-attrs)))
+ _ (when initial (typecheck-exp initial updated-symbols))]
+ {:declaration d
+ :ident->symbol updated-symbols})))
+
+(defn- typecheck-declaration [{:keys [declaration-type identifier] :as d} ident->symbol]
+ (let [at-top-level? (:at-top-level ident->symbol)]
+ (condp = declaration-type
+ :variable (if at-top-level?
+ (typecheck-file-scope-variable-declaration d ident->symbol)
+ (typecheck-local-scope-variable-declaration d ident->symbol))
+ :function (typecheck-function-declaration d ident->symbol)
+ (throw (ex-info "Analyzer Error. Invalid declaration for typechecker." {:declaration d})))))
(defn- typecheck-optional-expression [e decl-name->symbol]
(if (nil? e)
e
(typecheck-exp e decl-name->symbol)))
-
(defn- typecheck-for-init [for-init decl-name->symbol]
(if (= (:type for-init) :declaration)
(typecheck-declaration for-init decl-name->symbol)
@@ -373,25 +567,33 @@
:empty s
(throw (ex-info "Analyzer Error. Invalid statement type in typechecker." {:statement s}))))
-(defn- typecheck-item [{:keys [type] :as item} decl-name->symbol]
+(defn- typecheck-item [{:keys [type] :as item} ident->symbol]
(condp = type
- :declaration (let [v (typecheck-declaration item decl-name->symbol)]
+ :declaration (let [v (typecheck-declaration item ident->symbol)]
{:block-item (:declaration v)
- :decl-name->symbol (:decl-name->symbol v)})
- :statement {:block-item (typecheck-statement item decl-name->symbol)
- :decl-name->symbol decl-name->symbol}
- (throw (ex-info "Analyzer Error. Invalid statement/declaration." {item item}))))
+ :ident->symbol (:ident->symbol v)})
+ :statement {:block-item (typecheck-statement item ident->symbol)
+ :ident->symbol ident->symbol}
+ (exc/analyzer-error "Invalid statement/declaration." {item item})))
(defn- typecheck-block
+ "Typechecks a block with a given symbol table.
+
+ ident->symbol holds identifier to symbol mapping.
+ Symbol contains the type information, generated variable name etc.
+
+ | key | description |
+ |----------------|-------------|
+ |`:at-top-level` | Is current level top or not ( default true)|"
([block]
- (typecheck-block block {}))
- ([block decl-name->symbol]
+ (typecheck-block block {:at-top-level true}))
+ ([block ident->symbol]
(reduce (fn [acc item]
- (let [v (typecheck-item item (:decl-name->symbol acc))]
+ (let [v (typecheck-item item (:ident->symbol acc))]
{:block (conj (:block acc) (:block-item v))
- :decl-name->symbol (:decl-name->symbol v)}))
+ :ident->symbol (:ident->symbol v)}))
{:block []
- :decl-name->symbol decl-name->symbol}
+ :ident->symbol ident->symbol}
block)))
(defn validate [ast]
@@ -409,16 +611,22 @@
(comment
- (pp/pprint
(validate-from-src
"
-int foo(void);
-int main(void) {
-return foo();
+int twice(int x){
+ return 2 * x;
}
-int foo(void) {
-return 3;
+")
+
+ (validate-from-src
+ "
+int main(void) {
+ int a = 3;
+ {
+ int a = a = 4;
+ return a;
+ }
}
-"))
+")
- ())
+ ())
diff --git a/src/cljcc/exception.clj b/src/cljcc/exception.clj
index 6eec308..9ea5f31 100644
--- a/src/cljcc/exception.clj
+++ b/src/cljcc/exception.clj
@@ -7,3 +7,6 @@
(defn parser-error [msg data]
(throw (ex-info msg (merge {:error/type :parser} data))))
+
+(defn analyzer-error [msg data]
+ (throw (ex-info msg (merge {:error/type :analyzer} data))))
diff --git a/src/cljcc/parser.clj b/src/cljcc/parser.clj
index 84e681c..4ae441a 100644
--- a/src/cljcc/parser.clj
+++ b/src/cljcc/parser.clj
@@ -26,11 +26,6 @@
[_ tokens] (expect end-kind tokens)]
[e tokens])))
-(defn- keyword->type [k]
- (condp = k
- :kw-int "int"
- (throw (ex-info "Parser Error. Unsupported type." {:keyword k}))))
-
(defn- expect
"Expects the first token in list to be of given kind.