Написание программы на Haskell для программ проверки типов, написанных на императивном языке программирования

Я пытаюсь написать программу на Хаскелле для проверки типов программ, написанных на обязательном языке программирования.

Вот абстрактный синтаксис:


    type Name = String

- программа - это серия (список) объявлений переменных и серия (список) операторов.

    type Prog = ([TypeEnv],[Stmt])

- объявление переменной является типом и именем переменной

    type TypeEnv = (Type,Name)

- типом является либо "int", либо "bool", либо "int[]..[]" или "bool [].. []"

   data Type = BaseType BT | ArrayType BT Int deriving Show
   data BT = TyInt | TyBool deriving Show

- заявление либо...

   data Stmt =
   Assign Name Exp                -- ...assignment (<name> := <exp>;)
   | If Exp [Stmt] [Stmt]           -- ...if-then-else (if <bexp> { <stmt>* } else { <stmt>* })
   | While Exp [Stmt]               -- ...a while-loop (while <bexp> { <stmt>*> })
   | Let Name Exp [Stmt]            -- ...let bindings (let <name>=<exp> in { <stmt> *}) 
   | LetArray Name [Exp] Exp [Stmt] -- ...let-array binding (letarray <name> [ <exp> ] .. [ <exp> ] := <exp> in { <stmt>* })
   | Case Exp [(Int,[Stmt])]        -- ...a case statements
   | For Name Exp Exp [Stmt]        -- ...a for-loop
   | ArrayAssign Name [Exp] Exp       -- ...or array assignment (<name> [ <exp> ] .. [ <exp> ] := <exp>;)
   deriving Show

- выражение либо...

data Exp =
Add Exp Exp             -- ...addition (<exp> + <exp>)
| Sub Exp Exp             -- ...subtract (<exp> - <exp>)
| Mul Exp Exp             -- ...multiplication (<exp> * <exp>)
| Neg Exp                 -- ...negation (-<exp>)
| Var Name                -- ...a variable (<name>)
| LitInt Int              -- ...an integer literal (e.g. 3, 0, 42, 1999)
| VarArray Name [Exp]     -- ...or an array lookup (<name> [ <exp> ])
| IsEq Exp Exp            -- ...test for equality (<exp> == <exp>)
| IsNEq Exp Exp           -- ...test for inequality (<exp> != <exp>)
| IsGT Exp Exp            -- ...test for greater-than (<exp> > <exp>)
| IsLT Exp Exp            -- ...test for less-than (<exp> < <exp>)
| IsGTE Exp Exp           -- ...test for greater-or-equal (<exp> >= <exp>)
| IsLTE Exp Exp           -- ...test for less-or-equal (<exp> <= <exp>)
| And Exp Exp             -- ...boolean and (<bexp> && <bexp>)
| Or Exp Exp              -- ...boolean or (<bexp> || <bexp>)
| Not Exp                 -- ...boolean negation (!<bexp>)
| LitBool Bool            -- ... or a boolean literal (true or false)
deriving Show

Мне не нужен кто-то, чтобы полностью ответить на мой вопрос, но я хотел бы предоставить то, что у меня есть, и если кто-то может указать мне правильное направление или сообщить мне, если я делаю это совершенно неправильно, это было бы очень полезно,

Функция проверки типа программы начинается с проверки типа. typecheck использует typecheckstmt для проверки типов первого оператора и typecheckstmtlist для проверки типов остальной части программы. Затем эти функции используют typecheckexp для проверки типов любых выражений. Очевидно, у меня есть очень простой каркас реализации. Я просто хочу знать, двигаюсь ли я в правильном направлении, и есть ли у кого-нибудь указатели.


   typecheck :: Prog -> Bool
   typecheck _ = True
   typecheck (types, x: xs) = (typecheckstmt types x) && (typecheckstmtlist types xs)

   typecheckstmt :: [TypeEnv] -> Stmt -> Bool
   typecheckstmt _ _ = True
   typecheckstmt types (Assign x e) = if checkequaltypes x e
                  then True && typecheckexp types e
                  else False
   typecheckstmt types (If e stmtlst1 stmtlst2) = typecheckexp types e 
                       && typecheckstmtlist types stmtlst1 
                       && typecheckstmtlist types stmtlst2
   typecheckstmt types (While e stmtlst) = typecheckexp types e 
                    && typecheckstmtlist types stmtlst
   typecheckstmt types (Let x e stmtlst) = if checkequaltype types x e
                       then True && typecheckexp types e
                             && typecheckstmtlist types stmtlst
                       else False
   typecheckstmt types (LetArray x es e2 stmtlst) = 
   typecheckstmt types (Case e cases) = 
   typecheckstmt types (For x e1 e2 stmtlst) = if checkequaltype types x e1 
                           && checkequaltype types x e2 
                           then True && typecheckstmtlist stmtlst 
                           else False
   typecheckstmt types (ArrayAssign x es e2) = 

   typecheckstmtlist :: [TypeEnv] -> [Stmt] -> Bool
   typecheckstmtlist _ _ = True
   typecheckstmtlist types [x] = typecheckstmt types x
   typecheckstmtlist types x:xs = typecheckstmt types x && typecheckstmtlist types xs


   typecheckexp :: [TypeEnv] -> Exp -> Bool
   typecheckexp types (Add e1 e2) = 
   typecheckexp types (Sub e1 e2) =
   typecheckexp types (Mul e1 e2) =
   typecheckexp types (Neg e1) =
   typecheckexp types (Var x) =
   typecheckexp types (LitInt i) = 
   typecheckexp types (VarArray x explist) =
   typecheckexp types (IsEq e1 e2) = 
   typecheckexp types (IsNEq e1 e2) = 
   typecheckexp types (IsGT e1 e2) = 
   typecheckexp types (IsLT e1 e2) = 
   typecheckexp types (IsGTE e1 e2) =
   typecheckexp types (IsLTE e1 e2) = 
   typecheckexp types (And e1 e2) =
   typecheckexp types (Or e1 e2) = 
   typecheckexp types (Not e) = 
   typecheckexp types (LitBool Bool) = 

   typecheckexplist :: [TypeEnv] -> [Exp] -> Bool
   typecheckexplist _ _ = True
   typecheckexplist types [x] = typecheckexp types x
   typecheckexplist types x:xs = typecheckexp types x && typecheckexplist types xs

   checkequaltype :: [TypeEnv] -> Name -> Exp -> Bool
   checkequaltype types x e = getTypeOfVar types x && getTypeOfExp types e

   getTypeOfVar :: [TypeEnv] -> Name -> Type

   getTypeOfExp :: [TypeEnv] -> Exp -> Type

Я также немного неясен относительно того, что нужно проверять. Очевидно, что если вы назначаете и сравниваете переменные / выражения, вы хотите, чтобы они были одного типа.

Любая помощь приветствуется.

1 ответ

Решение

Поскольку конкретного вопроса нет, я просто предложу несколько общих советов по этой проблеме.

Похоже, вы на правильном пути. Ваш подход верен, вам нужно пройтись по синтаксическому дереву и проверить каждое подвыражение, если ваши типы не совпадают.

typecheckexp :: [TypeEnv] -> Exp -> Bool
typecheckexp types (Add e1 e2) = 
  case (te1, te2) of
    (Just TyInt, Just TyInt) -> True
     _ -> False
  where
    te1 = getTypeOfExp e1
    te2 = getTypeOfExp e2

На верхнем уровне вы примените проверку уровня выражения ко всем выражениям, а затем and все результаты вместе, чтобы получить, проверяет ли ваша программа в целом.

typecheckexplist :: [TypeEnv] -> [Exp] -> Bool
typecheckexplist env stmts = and (map (typecheckexp env) stmts)

Если все ваши типы объявлены заранее, а TypeEnv не изменен в результате перемещения по AST, тогда этот подход будет работать. Если вы создаете определения, проходя по дереву, подумайте о том, чтобы обернуть своего проверщика типов в монаду State.

Очевидно, что если вы назначаете и сравниваете переменные / выражения,

В зависимости от вашего языка интерфейса вам нужно решить, добавлять ли явные объявления типов (т.е. int a) для переменных или попытаться вывести их из контекста программы, что является отдельной задачей, называемой выводом типа. Если у вас есть явные объявления от пользователя, вы можете просто механически проверить данные типы на предмет использования переменной и определить, соответствуют ли они. Ваши типы все простые монотипии, так что это легко, как вы можете прикрепить (derving Eq) на ваш Type и получите сравнение типов.

Еще один случай, который следует рассмотреть, - это сообщение об ошибке, поскольку в заданном вами AST нет прикрепленной информации о положении, поэтому, если вы пройдете по дереву и потерпите неудачу на полпути, у вас не будет возможности сообщить пользователю, что и где не удалось. Если вы анализируете язык интерфейса из парсера, такого как Parsec, вы можете пометить каждый тип данных (Expr Pos) с информацией при построении синтаксического дерева.

data Expr t = Add t (Expr t) (Expr t) | ... 
data Pos = Pos { line :: Integer , col :: Integer }

Для простоты использования вы можете заглянуть в универсальную библиотеку, такую ​​как Uniplate, которая позволит вам применять функции и обходить AST без лишних шаблонов. Придуманный пример для извлечения всех узлов определенного типа может быть:

{-# LANGUAGE DeriveDataTypeable #-}
module Expr where

import Data.Data
import Data.Typeable
import Data.Generics.Uniplate.Data

data Expr = Val String
          | Add Expr Expr
          | Sub Expr Expr
          | Div Expr Expr
          | Mul Expr Expr
          | Neg Expr
          deriving (Show, Eq, Data, Typeable)

vars :: Expr -> [String]
vars ex = [i | Val i <- universe ex]

test :: [String]
test = vars (Add (Val "a") (Mul (Val "b") (Val "c")))
Другие вопросы по тегам