Automate Student Grading using Haskell
Haskell main program - Main.hs
This project takes a Pseudo code for Students assignment in a file which has some basic Add/Multiply set of expressions and helps the Professor pre-screen by running students assigment to see the completeness of Assignment.
It will give error with messages if somethign is wrong.
If its correct there is no error messages and flags and final Stack will have the final computation.
This below is valid assignment.
Bytecode File |
---|
LOAD_VAL 1 |
WRITE_VAR X |
LOAD_VAL 2 |
WRITE_VAR Y |
READ_VAR X |
LOAD_VAL 1 |
ADD |
READ_VAR Y |
MULTIPLY |
RETURN_VALUE |
Rules |
---|
Return_value will be last. Anything other place has to error |
Write - every write needs to have preceeding Load so that the variable is stored. Otherwise there will be error. |
Load without Write right after means its ready to be used in Add/Mutiply |
Add/Multiply will operate on 2 stored valus on stack. If its missing that's an error. Once its computed the prior 2 values are erased as its consumed and current value will be stored for future consumption. |
MonadIO m, MonadReader [ByteCodeIndexed] m, MonadState StateStack m
we start by reading the student's assignment file and unpacking each instruction into our custom data type ByteCode.
-- sample Student assignment
Load_val 1
Write_var X
Load_val 22
Write_var Y
Read_var X
Load_val 5
Add
Read_var Y
Multiply
Return_value
-- Reads the file and gets Text.Text
-- then we unpack it to String using `unPack`
-- then we convert to ByteCode data type with `wordsStringAsg`
initByte :: MonadIO m => m [ByteCode Int String]
initByte = do
ls <- io (fmap Text.lines (Text.readFile "src/assg1.txt"))
bytecodeFile <- return (wordsStringAsg (unPack ls))
return bytecodeFile
Then due to the nature of looking back to see if Write has a preceding Load etc. we now move the ByteCode to index each instruction into ByteCodeIndexed list. This way when there is a Write instruction we make immediately preceding there is a Load instruction.
transformBytecodeAddIndex :: Int -> [ByteCode Int String] -> [ByteCodeIndexed]
transformBytecodeAddIndex _ [] = []
transformBytecodeAddIndex i [bc] = [(bc,i)]
transformBytecodeAddIndex i (bc:bcs) = (bc,i) : transformBytecodeAddIndex (i+1) bcs
bytecodeIndexList :: MonadIO m => [ByteCode Int String] -> Int -> m [ByteCodeIndexed]
bytecodeIndexList asgList i = return (transformBytecodeAddIndex i asgList)
Assignment is converted to:
******************* Assignment with index:
[(Load_val 1,1),(Write_var "X",2),(Load_val 22,3),(Write_var "Y",4),(Read_var "X",5),(Load_val 5,6),(Add,7),(Read_var "Y",8),(Multiply,9),(Return_value,10)]
First we update the state writeIndex with all the writes we have.
Then we process all the Load instructions and put indexs on each Load.
Next step we create the Variables list for the writes but also do error handling to make sure Write precedes with Load. At this point we have all our varibles.
Now we execute the compute
function that reads each instruction and starts building and consuming the StackComputation
-- uses Monad Reader to get the ByteCodeIndexed
-- calls handleWrite which indexes all the Write instructions into State writesIndex
-- calls handleLoad which indexed all the Load instructions into the State loadIndex
-- calls handleVariables - this uses Write to get preceding Load to store as variables List
-- which can be accessed later as Read. Also removes the Load index since we wont use that Load fo consumption
-- in future as these will be accessed with Read.
-- calls compute which parses instruction one by one and either pushes in to Stack computation
-- or uses for consumption and stores in stack after value is computed.
operations :: (MonadIO m, MonadReader [ByteCodeIndexed] m, MonadState StateStack m) => m ()
operations = do
byteAsgIndexed <- ask
handleWrite byteAsgIndexed
handleLoad byteAsgIndexed
-- renderState
handleVariables byteAsgIndexed
-- renderState
io . putStrLn $ "******************* Final State "
compute byteAsgIndexed
renderState
A Load (only the non Load/write combo are considered) or Read will result is Pushing the value into the stack computation to be consumed later.
An Add or Multiply will consume 2 from the stack and will be popped out of the stack but now its current calculation is pushed onto the stack for furhter computation.