HomeMAL' // MANUFACTORUM
datacrypt
Piton / src / typechecker.rkt
src/typechecker.rkt13.7 KB
#lang racket/base

(require racket/match
         racket/list
         racket/hash
         (prefix-in ast- "ast.rkt")
         (prefix-in std- "types.rkt")
         (prefix-in std- "typed-builtin.rkt")
         "errors.rkt")
(provide typecheck)
(require racket/trace)

;; <returns> ast
(define (typecheck ast)
  ; local and built-in scopes (no global)
  (define symtables (list (hash) std-lib))
  (let-values ([(ast _) (typecheck-stmts ast symtables std-none)])
    ast))

;; <returns> type for id or fails if not declared
(define (resolve-id id pos symtables)
  (when (empty? symtables)
    (raise-undeclared-name-error pos id))
  (define symtable (first symtables))
  (if (hash-has-key? symtable id)
      (hash-ref symtable id)
      (resolve-id id pos (rest symtables))))

;; Group of statements
;; <returns> statements, symtables
(define (typecheck-stmts stmts symtables expected-return-type [allow-funcdef? #t])
  (for/fold ([stmts (list)]
             [symtables symtables])
            ([stmt stmts])
    #:final (ast-pos-return? stmt) ; everything after return is ignored
    (let-values ([(stmt symtables) (typecheck-stmt stmt symtables expected-return-type allow-funcdef?)])
      (values (append stmts (list stmt))
              symtables))))

;; Statement
;; <returns> statement, symtables
(define (typecheck-stmt stmt symtables expected-return-type [allow-funcdef? #t])
  (match stmt
    ; variable declaration
    [(ast-pos-decl id type val pos)
     (typecheck-decl id type val pos symtables)]
    ; assignation
    [(ast-pos-assign target val pos)
     (typecheck-assign target val pos symtables)]
    ; return
    [(ast-pos-return value pos)
     (typecheck-return value pos symtables expected-return-type)]
    ; if
    [(ast-pos-if test body else-body pos)
     (typecheck-if test body else-body pos symtables expected-return-type)]
    ; while
    [(ast-pos-while test body pos)
     (typecheck-while test body pos symtables expected-return-type)]
    ; function definition
    [(ast-pos-funcdef id return-type params body pos)
     (when (not allow-funcdef?)
       (raise-funcdef-not-allowed-error pos))
     (typecheck-funcdef id return-type params body pos symtables)]
    ; anything else is an expression
    [_
     (let-values ([(stmt type) (typecheck-expr stmt symtables)])
       (values stmt
               symtables))]))

;; Variable declaration
;; <returns> statement, symtables
(define (typecheck-decl id type val pos symtables)
  (define local-symtable (first symtables))
  ; check for existing symbol with same id
  (when (hash-has-key? local-symtable id)
    (raise-name-already-declared-error pos id))
  ; typecheck value expression
  (let-values ([(val val-type) (typecheck-expr val symtables)])
    ; check value type matches
    (when (not (equal? type val-type))
      (raise-assign-type-error pos id type val-type))
    ; update local symtable (after value typecheck, to prevent self-assign)
    (let* ([local-symtable (hash-set local-symtable id type)]
           [symtables (cons local-symtable (rest symtables))])
      (values (ast-decl id type val)
              symtables))))

;; Assignation
;; <returns> statement, values
(define (typecheck-assign target val pos symtables)
  ; typecheck value expression
  (define target-type (resolve-id target pos symtables))
  (let-values ([(val val-type) (typecheck-expr val symtables)])
    ; forbid function reassign
    (when (std-func? target-type)
      (raise-assign-func-error pos))
    ; check value type matches
    (when (not (equal? target-type val-type))
      (raise-assign-type-error pos target target-type val-type))
    (values (ast-assign target val)
            symtables)))

;; Return
;; <returns> statement, symtables
(define (typecheck-return val pos symtables expected-return-type)
  ; return without value: default to none
  (let ([val (or val (ast-pos-const 'none pos))])
    ; typecheck value expression
    (let-values ([(val val-type) (typecheck-expr val symtables)])
      ; check value type matches expected return type
      (unless (equal? val-type expected-return-type)
        (raise-return-type-error pos val-type expected-return-type))
      (values (ast-return val)
              symtables))))

;; If
;; <returns> statement, symtables
(define (typecheck-if test body else-body pos symtables expected-return-type)
  ; typecheck test expression
  (let-values ([(test test-type) (typecheck-expr test symtables)])
    ; convert test to bool
    (let ([test
           (match test-type
             [(== std-int)   test]
             [(== std-bool)  test]
             [(== std-str)   (ast-call "str_to_bool" (list test))]
             [(== std-none)  (ast-call "none_to_bool" (list test))]
             [(std-func _ _) (ast-call "func_to_bool" (list test))])])
      ; typecheck body statements
      (let-values ([(body if-symtables) (typecheck-stmts body symtables expected-return-type #f)])
        (if (not else-body)
            (values (ast-if test body else-body)
                    if-symtables)
            ; typecheck else-body statements if present
            (let-values ([(else-body else-symtables) (typecheck-stmts else-body symtables expected-return-type #f)])
              ; merge if/else symtables
              (define merged-symtable (merge-if-else-symtables (first if-symtables)
                                                               (first else-symtables) pos))
              (values (ast-if test body else-body)
                      (cons merged-symtable (rest symtables)))))))))

;; <returns> unique symtable and check type consistency
(define (merge-if-else-symtables if-symtable else-symtable pos)

  (hash-union if-symtable else-symtable
              #:combine/key (lambda (id if-type else-type)
                              (if (equal? if-type else-type)
                                  if-type
                                  (raise-type-consistency-error pos id if-type else-type)))))

;; While
;; <returns> statement, symtables
(define (typecheck-while test body pos symtables expected-return-type)
  ; typecheck test expression
  (let-values ([(test test-type) (typecheck-expr test symtables)])
    ; convert test to bool
    (let ([test
           (match test-type
             [(== std-int)   test]
             [(== std-bool)  test]
             [(== std-str)   (ast-call "str_to_bool" (list test))]
             [(== std-none)  (ast-call "none_to_bool" (list test))]
             [(std-func _ _) (ast-call "func_to_bool" (list test))])])
      ; typecheck body statements
      (let-values ([(body symtables) (typecheck-stmts body symtables expected-return-type #f)])
        (values (ast-while test body)
                symtables)))))

;; Function definition
;; <returns> statement, symtables
(define (typecheck-funcdef id return-type params body pos symtables)
  (define local-symtable (first symtables))
  ; check for existing symbol with same id
  (when (hash-has-key? local-symtable id)
    (raise-name-already-declared-error pos id))

  ; update local symtable (before body typecheck, to allow recursivity)
  (let* ([params-types (map (lambda (param) (match param [(ast-pos-param _ type _) type]))
                            params)]
         [type (std-func return-type params-types)])
    (let* ([local-symtable (hash-set local-symtable id type)]
           [symtables (cons local-symtable (rest symtables))])
      ; new symtable for function body with functions params
      (define inner-symtable (hash))
      (define inner-symtables (cons inner-symtable symtables))
      ; typecheck parameters with inner symbol tables
      (let-values ([(params inner-symtables) (typecheck-params params pos inner-symtables)])
        ; return-type defaults to none
        (let ([return-type (or return-type std-none)])
          ; typecheck body with inner symbol tables and expected return type
          (let-values ([(body _) (typecheck-stmts body inner-symtables return-type)])
            (values (ast-funcdef id return-type params body)
                    symtables)))))))

;; Function params
;; <returns> statements, symtables
(define (typecheck-params params pos symtables)
  (define local-symtable (first symtables))
  (let-values
      ([(params local-symtable)
        (for/fold ([params (list)]
                   [local-symtable local-symtable])
                  ([param params])
          (match param
            [(ast-pos-param id type pos)
             ; check for existing param with same id
             (when (hash-has-key? local-symtable id)
               (raise-duplicate-param-error pos id))
             (let ([param (ast-param id type)])
               (values (append params (list param))
                       (hash-set local-symtable id type)))]))])
    (values params
            (cons local-symtable (rest symtables)))))

;; Expression
;; <returns> statement, type
(define (typecheck-expr expr symtables)
  (match expr
    [(ast-pos-num val pos)
     (typecheck-num val pos)]
    [(ast-pos-const val pos)
     (typecheck-const val pos)]
    [(ast-pos-str val pos)
     (typecheck-str val pos)]
    [(ast-pos-name id pos)
     (typecheck-name id pos symtables)]
    [(ast-pos-binoper op lhs rhs pos)
     (typecheck-binoper op lhs rhs pos symtables)]
    [(ast-pos-unoper op operand pos)
     (typecheck-unoper op operand pos symtables)]
    [(ast-pos-subscript value index pos)
     (typecheck-subscript value index pos symtables)]
    [(ast-pos-call func args pos)
     (typecheck-call func args pos symtables)]))

;; Num
;; <returns> expression, type
(define (typecheck-num val pos)
  ; only integers are supported
  (values (ast-num val)
          std-int))

;; Const
;; <returns> statement, type
(define (typecheck-const val pos)
  (match val
    [(or 'true 'false) (values (ast-const val)
                               std-bool)]
    ['none             (values (ast-const std-none)
                               std-none)]))

;; String
;; <returns> statement, type
(define (typecheck-str val pos)
  (values (ast-str val)
          std-str))

;; Name
;; <returns> statement, type
(define (typecheck-name id pos symtables)
  (define type (resolve-id id pos symtables))
  (values (ast-name id)
          type))

;; Binary operation
;; all operations are replaced by calls
;; <returns> statement, type
(define (typecheck-binoper op lhs rhs pos symtables)
  ; typecheck operands
  (let-values ([(lhs lhs-type) (typecheck-expr lhs symtables)]
               [(rhs rhs-type) (typecheck-expr rhs symtables)])
    ; identify operation using operator and operand types
    (define op-key (list op lhs-type rhs-type))
    (when (not (hash-has-key? std-ops op-key))
      (raise-binop-type-error pos op lhs-type rhs-type))
    ; replace operation by call to internal function
    (let ([func-desc (hash-ref std-ops op-key)])
      (define func-name (car func-desc))
      (define return-type (cdr func-desc))
      (values (ast-call func-name (list lhs rhs))
              return-type))))

;; Unary operation
;; all operations are replaced by calls
;; <returns> statement, type
(define (typecheck-unoper op operand pos symtables)
  ; typecheck operand
  (let-values ([(operand operand-type) (typecheck-expr operand symtables)])
    ; identify operation using operator and operand types
    (define op-key (list op operand-type))
    (when (not (hash-has-key? std-ops op-key))
      (raise-unop-type-error pos op operand-type))
    ; replace operation by call to internal function
    (let ([func-desc (hash-ref std-ops op-key)])
      (define func-name (car func-desc))
      (define return-type (cdr func-desc))
      (values (ast-call func-name (list operand))
              return-type))))

;; Subscript
;; all subscripts accesses are replaced by calls
;; <returns> statement, type
(define (typecheck-subscript value index pos symtables)
  ; check value is a string (TODO or a list)
  (let-values ([(value value-type) (typecheck-expr value symtables)])
    (match value-type
      ; string: get char index
      [(== std-str)
       ; typecheck index expression
       (let-values ([(index index-type) (typecheck-expr index symtables)])
         ; check index type is int or bool
         (let ([index
                (match index-type
                  [(== std-int)  index]
                  [(== std-bool) index];(ast-call "bool_to_int" (list index))]
                  [_ (raise-invalid-index-type-error pos value-type)])])
           (values (ast-call "char_at_index" (list value index))
                   std-str)))] ; returns a string
      ; TODO list
      ; not subscriptable
      [_ (raise-not-subscriptable-error pos value-type)])))

;; Call
;; <returns> statement, type
(define (typecheck-call callee args pos symtables)
  ; check callee is a function
  (define callee-type (resolve-id callee pos symtables))
  ;(let-values ([(callee callee-type) (typecheck-expr callee symtables)])
  (match callee-type
    ; function
    [(std-func return-type param-types)
     ; typecheck args
     (let-values ([(args _) (typecheck-args param-types args pos symtables)])
       (values (ast-call callee args)
               return-type))]
    ; not callable
    [_ (raise-not-callable-error pos callee-type)]))

;; Call arguments
;; <params-types> types of formal parameters
;; <args> actual arguments
;; <returns> statements, types
(define (typecheck-args params-types args pos symtables)
  ; check arity
  (define params-count (length params-types))
  (define args-count (length args))
  (when (not (equal? params-count args-count))
    (raise-arity-error pos params-count args-count))
  ; check arguments
  (for/lists (args types) ([param-type params-types]
                           [arg args])
    ; typecheck value expression
    (define-values (expr expr-type) (typecheck-expr arg symtables))
    ; check types matches parameter
    (unless (equal? param-type expr-type)
      (raise-arg-type-error pos param-type expr-type))
    (values expr
            expr-type)))