Compare commits
51 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
e5784bb20b
|
|||
|
dba2c2b3d6
|
|||
|
d042a0e016
|
|||
|
d2bbb8d14a
|
|||
|
574225a0f3
|
|||
|
95bb649249
|
|||
|
c11daa808f
|
|||
|
b7285c39d6
|
|||
|
1c5df94c31
|
|||
|
6d6820009b
|
|||
|
6aa6c9f076
|
|||
|
f0c3ba65a8
|
|||
|
aec8289f11
|
|||
|
c021b2e24e
|
|||
|
9374144565
|
|||
|
4a784531c2
|
|||
|
d41f780d2e
|
|||
|
7b54fa5303
|
|||
|
4fd0fec404
|
|||
|
2ef46d20c0
|
|||
|
7661769b52
|
|||
|
091481091b
|
|||
|
dc881101eb
|
|||
|
a5845d69b6
|
|||
|
f5788b3f4a
|
|||
|
1bc674f6b6
|
|||
|
7541f30384
|
|||
|
82e51d2224
|
|||
|
471c65d1d3
|
|||
|
32760466db
|
|||
|
9662155716
|
|||
|
aff81eb6da
|
|||
|
022f9ca2fc
|
|||
|
d3479a5b88
|
|||
|
5e9759a8e5
|
|||
|
d7f35fef5b
|
|||
|
8d9529d7c6
|
|||
|
70c88a8ba2
|
|||
|
23b2a4376d
|
|||
|
d0f0d271ea
|
|||
|
3a4f8bbde6
|
|||
|
4e8118bfc0
|
|||
|
d5b3fdf33a
|
|||
|
c481875eca
|
|||
|
5138e3f817
|
|||
|
fd9063e09d
|
|||
|
8b04b13c8f
|
|||
|
54bf0bfd56
|
|||
|
09f11f0e8b
|
|||
|
52e7e119b6
|
|||
|
7911a8d160
|
120
hw1/BigNum.hs
Normal file
120
hw1/BigNum.hs
Normal file
@@ -0,0 +1,120 @@
|
||||
{-
|
||||
Name: Yuri Tatishchev
|
||||
Class: CS 252
|
||||
Assigment: HW1
|
||||
Date: 2/16/2026
|
||||
Description: Big numbers in Haskell
|
||||
-}
|
||||
|
||||
module BigNum (
|
||||
BigNum,
|
||||
bigAdd,
|
||||
bigSubtract,
|
||||
bigMultiply,
|
||||
bigEq,
|
||||
bigDec,
|
||||
bigPowerOf,
|
||||
prettyPrint,
|
||||
stringToBigNum,
|
||||
) where
|
||||
|
||||
type Block = Int -- An Int from 0-999
|
||||
|
||||
type BigNum = [Block]
|
||||
|
||||
maxblock = 1000
|
||||
|
||||
bigAdd :: BigNum -> BigNum -> BigNum
|
||||
bigAdd x y = bigAdd' x y 0
|
||||
|
||||
bigAdd' :: BigNum -> BigNum -> Block -> BigNum
|
||||
bigAdd' [] [] 0 = []
|
||||
bigAdd' [] [] c = [c]
|
||||
bigAdd' (x:xs) (y:ys) c = let s = x + y + c
|
||||
in (s `mod` maxblock) : bigAdd' xs ys (s `div` maxblock)
|
||||
bigAdd' (x:xs) [] c = let s = x + c
|
||||
in (s `mod` maxblock) : bigAdd' xs [] (s `div` maxblock)
|
||||
bigAdd' [] (y:ys) c = let s = y + c
|
||||
in (s `mod` maxblock) : bigAdd' [] ys (s `div` maxblock)
|
||||
|
||||
bigSubtract :: BigNum -> BigNum -> BigNum
|
||||
bigSubtract x y =
|
||||
if length x < length y
|
||||
then error "Negative numbers not supported"
|
||||
else reverse $ stripLeadingZeroes $ reverse result
|
||||
where result = bigSubtract' x y 0
|
||||
|
||||
stripLeadingZeroes :: BigNum -> BigNum
|
||||
stripLeadingZeroes (0:[]) = [0]
|
||||
stripLeadingZeroes (0:xs) = stripLeadingZeroes xs
|
||||
stripLeadingZeroes xs = xs
|
||||
|
||||
-- Negative numbers are not supported, so you may throw an error in these cases
|
||||
bigSubtract' :: BigNum -> BigNum -> Block -> BigNum
|
||||
bigSubtract' [] [] 0 = []
|
||||
bigSubtract' [] _ _ = error "Negative numbers not supported"
|
||||
bigSubtract' (x:xs) (y:ys) b = let d = x - y - b
|
||||
in if d < 0
|
||||
then (d + maxblock) : bigSubtract' xs ys 1
|
||||
else d : bigSubtract' xs ys 0
|
||||
bigSubtract' (x:xs) [] b = let d = x - b
|
||||
in if d < 0
|
||||
then (d + maxblock) : bigSubtract' xs [] 1
|
||||
else d : bigSubtract' xs [] 0
|
||||
|
||||
bigEq :: BigNum -> BigNum -> Bool
|
||||
bigEq x y = stripLeadingZeroes (reverse x) == stripLeadingZeroes (reverse y)
|
||||
|
||||
bigDec :: BigNum -> BigNum
|
||||
bigDec x = bigSubtract x [1]
|
||||
|
||||
-- Handle multiplication following the same approach you learned in grade
|
||||
-- school, except dealing with blocks of 3 digits rather than single digits.
|
||||
-- If you are having trouble finding a solution, write a helper method that
|
||||
-- multiplies a BigNum by an Int.
|
||||
bigMultiply :: BigNum -> BigNum -> BigNum
|
||||
bigMultiply x y = reverse $ stripLeadingZeroes $ reverse $ bigMultiply' x y
|
||||
|
||||
bigMultiply' :: BigNum -> BigNum -> BigNum
|
||||
bigMultiply' _ [] = []
|
||||
bigMultiply' x (y:ys) = bigAdd (bigMultiplyBlock x y) (bigMultiply (0:x) ys)
|
||||
|
||||
bigMultiplyBlock :: BigNum -> Block -> BigNum
|
||||
bigMultiplyBlock x y = bigMultiplyBlock' x y 0
|
||||
|
||||
bigMultiplyBlock' :: BigNum -> Block -> Block -> BigNum
|
||||
bigMultiplyBlock' [] _ 0 = []
|
||||
bigMultiplyBlock' [] _ c = [c]
|
||||
bigMultiplyBlock' (x:xs) y c = let p = x * y + c
|
||||
in (p `mod` maxblock) : bigMultiplyBlock' xs y (p `div` maxblock)
|
||||
|
||||
bigPowerOf :: BigNum -> BigNum -> BigNum
|
||||
bigPowerOf x [0] = [1]
|
||||
bigPowerOf x [1] = x
|
||||
bigPowerOf x y = bigMultiply x (bigPowerOf x (bigDec y))
|
||||
|
||||
prettyPrint :: BigNum -> String
|
||||
prettyPrint [] = ""
|
||||
prettyPrint xs = show first ++ prettyPrint' rest
|
||||
where (first:rest) = reverse xs
|
||||
|
||||
prettyPrint' :: BigNum -> String
|
||||
prettyPrint' [] = ""
|
||||
prettyPrint' (x:xs) = prettyPrintBlock x ++ prettyPrint' xs
|
||||
|
||||
prettyPrintBlock :: Int -> String
|
||||
prettyPrintBlock x | x < 10 = ",00" ++ show x
|
||||
| x < 100 = ",0" ++ show x
|
||||
| otherwise = "," ++ show x
|
||||
|
||||
stringToBigNum :: String -> BigNum
|
||||
stringToBigNum "0" = [0]
|
||||
stringToBigNum s = stringToBigNum' $ reverse s
|
||||
|
||||
stringToBigNum' :: String -> BigNum
|
||||
stringToBigNum' [] = []
|
||||
stringToBigNum' s | length s <= 3 = read (reverse s) : []
|
||||
stringToBigNum' (a:b:c:rest) = block : stringToBigNum' rest
|
||||
where block = read $ c:b:a:[]
|
||||
|
||||
sig = "9102llaf"
|
||||
36
hw1/Calculator.hs
Normal file
36
hw1/Calculator.hs
Normal file
@@ -0,0 +1,36 @@
|
||||
-- This is a simple parser for testing out BigNum.
|
||||
-- Note that there are much better parsers, which
|
||||
-- we will explore in another class.
|
||||
|
||||
import BigNum
|
||||
|
||||
-- Crude way of handling whitespace: make sure
|
||||
-- that all ops are surrounded by whitespace.
|
||||
addSpace :: String -> String
|
||||
addSpace [] = ""
|
||||
addSpace ('+':xs) = " + " ++ addSpace xs
|
||||
addSpace ('-':xs) = " - " ++ addSpace xs
|
||||
addSpace ('*':xs) = " * " ++ addSpace xs
|
||||
addSpace ('^':xs) = " ^ " ++ addSpace xs
|
||||
addSpace (x:xs) = x : addSpace xs
|
||||
|
||||
calculate :: String -> BigNum -> BigNum -> BigNum
|
||||
calculate "+" b1 b2 = bigAdd b1 b2
|
||||
calculate "-" b1 b2 = bigSubtract b1 b2
|
||||
calculate "*" b1 b2 = bigMultiply b1 b2
|
||||
calculate "^" b1 b2 = bigPowerOf b1 b2
|
||||
|
||||
main :: IO ()
|
||||
main = do
|
||||
line <- getLine
|
||||
if null line
|
||||
then return ()
|
||||
else do
|
||||
case words $ addSpace line of
|
||||
exp1:op:exp2:[] -> putStrLn $ prettyPrint $ calculate op big1 big2
|
||||
where big1 = stringToBigNum exp1
|
||||
big2 = stringToBigNum exp2
|
||||
exp:[] -> putStrLn $ show $ stringToBigNum exp
|
||||
_ -> putStrLn "Only simply binary expressions are supported"
|
||||
main
|
||||
|
||||
5
hw1/Test.java
Normal file
5
hw1/Test.java
Normal file
@@ -0,0 +1,5 @@
|
||||
public class Test {
|
||||
public void main(String[] args) {
|
||||
System.out.println(999999999999999999999 * 2);
|
||||
}
|
||||
}
|
||||
63
hw1/hw1.txt
Normal file
63
hw1/hw1.txt
Normal file
@@ -0,0 +1,63 @@
|
||||
For the first assignment, we will look at how Haskell handles big numbers.
|
||||
|
||||
***NOTE: YOU MAY NOT CHANGE ANY TYPE SIGNATURES***
|
||||
***IF YOU DO, YOU WILL GET A ZERO FOR THE ASSIGNMENT***
|
||||
|
||||
Consider the following Java program (available in Test.java).
|
||||
|
||||
public class Test {
|
||||
public void main(String[] args) {
|
||||
System.out.println(999999999999999999999 * 2);
|
||||
}
|
||||
}
|
||||
|
||||
You could easily calculate 999999999999999999999 * 2 with pencil and paper;
|
||||
Java cannot handle it.
|
||||
|
||||
$ javac Test.java
|
||||
Test.java:3: error: integer number too large: 999999999999999999999
|
||||
System.out.println(999999999999999999999 * 2);
|
||||
^
|
||||
1 error
|
||||
|
||||
With Haskell, there is no problem:
|
||||
|
||||
$ ghci
|
||||
GHCi, version 7.6.3: http://www.haskell.org/ghc/ :? for help
|
||||
Loading package ghc-prim ... linking ... done.
|
||||
Loading package integer-gmp ... linking ... done.
|
||||
Loading package base ... linking ... done.
|
||||
Prelude> 999999999999999999999 * 2
|
||||
1999999999999999999998
|
||||
Prelude>
|
||||
|
||||
So how does Haskell handle these numbers?
|
||||
We will implement a simplified BigNum module to understand it better.
|
||||
|
||||
In our implementation, a number will be a list of "blocks" of numbers from 0-999,
|
||||
stored with the least significant "block" first. So 9,073,201 will be
|
||||
stored as:
|
||||
|
||||
[201,73,9]
|
||||
|
||||
Your job is to complete BigNum.hs. The breakdown of points is as follows:
|
||||
* 10 points -- Complete bigAdd'
|
||||
* 5 points -- Complete bigSubtract'
|
||||
* 3 points -- Complete bigMultiply
|
||||
* 2 points -- Complete bigPowerOf
|
||||
|
||||
|
||||
Starter code is available on the course website.
|
||||
The files include:
|
||||
* BigNum.hs -- You will modify this file (only).
|
||||
* Calculator.hs -- A (very) simple calculator that relies on your BigNum module.
|
||||
* test.hs -- A number of test cases that use BigNum.
|
||||
* input -- A number of cases that Calculator.hs should handle correctly.
|
||||
* output_EXPECTED -- the expected results of calling (from the command line):
|
||||
$runhaskell test.hs
|
||||
$runhaskell Calculator.hs < input
|
||||
|
||||
Note that negative numbers are not supported, and should raise an error.
|
||||
|
||||
Submit BigNum.hs through Canvas.
|
||||
|
||||
7
hw1/input
Normal file
7
hw1/input
Normal file
@@ -0,0 +1,7 @@
|
||||
3 + 4
|
||||
9 * 7
|
||||
2 ^ 8
|
||||
400000000000000000001 * 2
|
||||
999999999999999999999 - 999999999999999999998
|
||||
483971285601 * 123448796045
|
||||
|
||||
26
hw1/output
Normal file
26
hw1/output
Normal file
@@ -0,0 +1,26 @@
|
||||
Addition
|
||||
[455,1]
|
||||
[455,3]
|
||||
[455,235,681]
|
||||
[455,235,681]
|
||||
[455,1,681]
|
||||
Subtraction
|
||||
[999]
|
||||
[962,634,9]
|
||||
[1]
|
||||
[0]
|
||||
Multiplication
|
||||
[12]
|
||||
[0]
|
||||
[392,296,4,12]
|
||||
[518,250,645,161,37,915,479,1]
|
||||
Power Of
|
||||
[256]
|
||||
[1]
|
||||
Others
|
||||
7
|
||||
63
|
||||
256
|
||||
800,000,000,000,000,000,002
|
||||
1
|
||||
59,745,672,527,794,294,248,045
|
||||
26
hw1/output_EXPECTED
Normal file
26
hw1/output_EXPECTED
Normal file
@@ -0,0 +1,26 @@
|
||||
Addition
|
||||
[455,1]
|
||||
[455,3]
|
||||
[455,235,681]
|
||||
[455,235,681]
|
||||
[455,1,681]
|
||||
Subtraction
|
||||
[999]
|
||||
[962,634,9]
|
||||
[1]
|
||||
[0]
|
||||
Multiplication
|
||||
[12]
|
||||
[0]
|
||||
[392,296,4,12]
|
||||
[518,250,645,161,37,915,479,1]
|
||||
Power Of
|
||||
[256]
|
||||
[1]
|
||||
Others
|
||||
7
|
||||
63
|
||||
256
|
||||
800,000,000,000,000,000,002
|
||||
1
|
||||
59,745,672,527,794,294,248,045
|
||||
50
hw1/test.hs
Normal file
50
hw1/test.hs
Normal file
@@ -0,0 +1,50 @@
|
||||
import BigNum
|
||||
|
||||
main :: IO ()
|
||||
main = do
|
||||
putStrLn "Addition"
|
||||
--999 + 456
|
||||
putStrLn $ show $ bigAdd [999] [456]
|
||||
--1999 + 1456
|
||||
putStrLn $ show $ bigAdd [999,1] [456,1]
|
||||
--681234999 + 456
|
||||
putStrLn $ show $ bigAdd [999,234,681] [456]
|
||||
--456 + 681234999
|
||||
putStrLn $ show $ bigAdd [456] [999,234,681]
|
||||
--681000999 + 456
|
||||
putStrLn $ show $ bigAdd [999,0,681] [456]
|
||||
|
||||
putStrLn "Subtraction"
|
||||
--1000 - 1
|
||||
putStrLn $ show $ bigSubtract [0,1] [1]
|
||||
--9643291 - 8329
|
||||
putStrLn $ show $ bigSubtract [291,643,9] [329,8]
|
||||
--999999 - 999998
|
||||
putStrLn $ show $ bigSubtract [999,999] [998,999]
|
||||
--10009 - 10009
|
||||
putStrLn $ show $ bigSubtract [9,10] [9,10]
|
||||
|
||||
----Error cases
|
||||
--putStrLn $ show $ bigSubtract [987] [0,1]
|
||||
--putStrLn $ show $ bigSubtract [9] [456]
|
||||
--putStrLn $ show $ bigSubtract [9] [10]
|
||||
--putStrLn $ show $ bigSubtract [9,999,999,999] [10,999,999,999]
|
||||
|
||||
putStrLn "Multiplication"
|
||||
--3 * 4
|
||||
putStrLn $ show $ bigMultiply [3] [4]
|
||||
--1987 * 0
|
||||
putStrLn $ show $ bigMultiply [987,1] [0]
|
||||
--3001074098 * 4
|
||||
putStrLn $ show $ bigMultiply [98,74,1,3] [4]
|
||||
--3001074098 * 493128456291
|
||||
putStrLn $ show $ bigMultiply [98,74,1,3] [291,456,128,493]
|
||||
|
||||
putStrLn "Power Of"
|
||||
--2^8
|
||||
putStrLn $ show $ bigPowerOf [2] [8]
|
||||
--1832^0
|
||||
putStrLn $ show $ bigPowerOf [832,1] [0]
|
||||
|
||||
putStrLn "Others"
|
||||
|
||||
5
hw1/test.sh
Executable file
5
hw1/test.sh
Executable file
@@ -0,0 +1,5 @@
|
||||
#!/bin/sh
|
||||
runhaskell test.hs > output
|
||||
runhaskell Calculator.hs < input >> output
|
||||
diff output output_EXPECTED
|
||||
|
||||
132
hw2/hs/WhileInterp.hs
Normal file
132
hw2/hs/WhileInterp.hs
Normal file
@@ -0,0 +1,132 @@
|
||||
{-
|
||||
Name: Yuri Tatishchev
|
||||
Class: CS 252
|
||||
Assigment: HW2
|
||||
Date: 2026-03-06
|
||||
Description: Implements the big-step operational semantics for
|
||||
the WHILE language described in `while-semantics.pdf`
|
||||
-}
|
||||
|
||||
|
||||
module WhileInterp (
|
||||
Expression(..),
|
||||
Binop(..),
|
||||
Value(..),
|
||||
testProgram,
|
||||
run
|
||||
) where
|
||||
|
||||
import Data.Map (Map)
|
||||
import qualified Data.Map as Map
|
||||
|
||||
-- We represent variables as strings.
|
||||
type Variable = String
|
||||
|
||||
-- The store is an associative map from variables to values.
|
||||
-- (The store roughly corresponds with the heap in a language like Java).
|
||||
type Store = Map Variable Value
|
||||
|
||||
data Expression =
|
||||
Var Variable -- x
|
||||
| Val Value -- v
|
||||
| Assign Variable Expression -- x := e
|
||||
| Sequence Expression Expression -- e1; e2
|
||||
| Op Binop Expression Expression
|
||||
| If Expression Expression Expression -- if e1 then e2 else e3
|
||||
| While Expression Expression -- while (e1) e2
|
||||
| BoolOp BoolBinop Expression Expression
|
||||
| Not Expression
|
||||
deriving (Show)
|
||||
|
||||
data Binop =
|
||||
Plus -- + :: Int -> Int -> Int
|
||||
| Minus -- - :: Int -> Int -> Int
|
||||
| Times -- * :: Int -> Int -> Int
|
||||
| Divide -- / :: Int -> Int -> Int
|
||||
| Gt -- > :: Int -> Int -> Bool
|
||||
| Ge -- >= :: Int -> Int -> Bool
|
||||
| Lt -- < :: Int -> Int -> Bool
|
||||
| Le -- <= :: Int -> Int -> Bool
|
||||
deriving (Show)
|
||||
|
||||
data BoolBinop =
|
||||
And
|
||||
| Or
|
||||
deriving (Show)
|
||||
|
||||
data Value =
|
||||
IntVal Int
|
||||
| BoolVal Bool
|
||||
deriving (Show)
|
||||
|
||||
|
||||
-- This function will be useful for defining binary operations.
|
||||
-- The first case is done for you.
|
||||
-- Be sure to explicitly check for a divide by 0 and throw an error.
|
||||
applyOp :: Binop -> Value -> Value -> Value
|
||||
applyOp Plus (IntVal i) (IntVal j) = IntVal $ i + j
|
||||
applyOp Minus (IntVal i) (IntVal j) = IntVal $ i - j
|
||||
applyOp Times (IntVal i) (IntVal j) = IntVal $ i * j
|
||||
applyOp Divide (IntVal i) (IntVal j) = IntVal $ i `div` j
|
||||
applyOp Gt (IntVal i) (IntVal j) = BoolVal $ i > j
|
||||
applyOp Ge (IntVal i) (IntVal j) = BoolVal $ i >= j
|
||||
applyOp Lt (IntVal i) (IntVal j) = BoolVal $ i < j
|
||||
applyOp Le (IntVal i) (IntVal j) = BoolVal $ i <= j
|
||||
applyOp _ _ _ = error "Not implemented for non-integer values"
|
||||
|
||||
applyBoolOp :: BoolBinop -> Value -> Value -> Value
|
||||
applyBoolOp And (BoolVal b1) (BoolVal b2) = BoolVal $ b1 && b2
|
||||
applyBoolOp Or (BoolVal b1) (BoolVal b2) = BoolVal $ b1 || b2
|
||||
applyBoolOp _ _ _ = error "Not implemented for non-boolean values"
|
||||
|
||||
applyNot :: Value -> Value
|
||||
applyNot (BoolVal b) = BoolVal $ not b
|
||||
applyNot _ = error "Not implemented for non-boolean values"
|
||||
|
||||
-- Implement this function according to the specified semantics
|
||||
evaluate :: Expression -> Store -> (Value, Store)
|
||||
evaluate (Val v) s = (v, s)
|
||||
evaluate (Var x) s = case Map.lookup x s of
|
||||
Just v -> (v, s)
|
||||
Nothing -> error "Variable not found"
|
||||
evaluate (Assign x e) s =
|
||||
let (v,s') = evaluate e s
|
||||
in (v, Map.insert x v s')
|
||||
evaluate (Sequence e1 e2) s =
|
||||
let (_,s1) = evaluate e1 s
|
||||
(v2,s') = evaluate e2 s1
|
||||
in (v2, s')
|
||||
evaluate (Op o e1 e2) s =
|
||||
let (v1,s1) = evaluate e1 s
|
||||
(v2,s') = evaluate e2 s1
|
||||
in (applyOp o v1 v2, s')
|
||||
evaluate (If e1 e2 e3) s =
|
||||
let (v1,s1) = evaluate e1 s
|
||||
in case v1 of
|
||||
BoolVal b -> if b then evaluate e2 s1 else evaluate e3 s1
|
||||
_ -> error "Not implemented for non-boolean values"
|
||||
evaluate (While e1 e2) s =
|
||||
let (v1,s1) = evaluate e1 s
|
||||
in case v1 of
|
||||
BoolVal b -> if b then
|
||||
let (_, s2) = evaluate e2 s1
|
||||
in evaluate (While e1 e2) s2
|
||||
else (BoolVal False, s1)
|
||||
_ -> error "Not implemented for non-boolean values"
|
||||
evaluate (BoolOp o e1 e2) s =
|
||||
let (v1,s1) = evaluate e1 s
|
||||
(v2,s') = evaluate e2 s1
|
||||
in (applyBoolOp o v1 v2, s')
|
||||
evaluate (Not e) s =
|
||||
let (v,s') = evaluate e s
|
||||
in (applyNot v, s')
|
||||
|
||||
-- Evaluates a program with an initially empty state
|
||||
run :: Expression -> (Value, Store)
|
||||
run prog = evaluate prog Map.empty
|
||||
|
||||
-- The same as run, but only returns the Store
|
||||
testProgram :: Expression -> Store
|
||||
testProgram prog = snd $ run prog
|
||||
|
||||
|
||||
12
hw2/hs/mapExample.hs
Normal file
12
hw2/hs/mapExample.hs
Normal file
@@ -0,0 +1,12 @@
|
||||
import Data.Map (Map)
|
||||
import qualified Data.Map as Map
|
||||
|
||||
m = Map.empty
|
||||
|
||||
m' = Map.insert "a" 42 m
|
||||
|
||||
main = do
|
||||
case (Map.lookup "a" m') of
|
||||
Just i -> putStrLn $ show i
|
||||
_ -> error "Key is not in the map"
|
||||
|
||||
36
hw2/hs/test.hs
Normal file
36
hw2/hs/test.hs
Normal file
@@ -0,0 +1,36 @@
|
||||
import WhileInterp
|
||||
|
||||
-- Here are a few tests that you can use to check your implementation.
|
||||
w_test = (Sequence (Assign "X" (Op Plus (Op Minus (Op Plus (Val (IntVal 1)) (Val (IntVal 2))) (Val (IntVal 3))) (Op Plus (Val (IntVal 1)) (Val (IntVal 3))))) (Sequence (Assign "Y" (Val (IntVal 0))) (While (Op Gt (Var "X") (Val (IntVal 0))) (Sequence (Assign "Y" (Op Plus (Var "Y") (Var "X"))) (Assign "X" (Op Minus (Var "X") (Val (IntVal 1))))))))
|
||||
|
||||
w_fact = (Sequence (Assign "N" (Val (IntVal 2))) (Sequence (Assign "F" (Val (IntVal 1))) (While (Op Gt (Var "N") (Val (IntVal 0))) (Sequence (Assign "X" (Var "N")) (Sequence (Assign "Z" (Var "F")) (Sequence (While (Op Gt (Var "X") (Val (IntVal 1))) (Sequence (Assign "F" (Op Plus (Var "Z") (Var "F"))) (Assign "X" (Op Minus (Var "X") (Val (IntVal 1)))))) (Assign "N" (Op Minus (Var "N") (Val (IntVal 1))))))))))
|
||||
|
||||
testUnit :: IO ()
|
||||
testUnit = do
|
||||
-- Should be: (IntVal 1,fromList [])
|
||||
putStrLn $ show $ WhileInterp.run (Val (IntVal 1))
|
||||
-- Should be: (BoolVal True,fromList [("X",BoolVal True)])
|
||||
putStrLn $ show $ WhileInterp.run (Assign "X" (Val (BoolVal True)))
|
||||
-- Should be: (IntVal 2,fromList [])
|
||||
putStrLn $ show $ WhileInterp.run (Sequence (Val (IntVal 1)) (Val (IntVal 2)))
|
||||
-- Should be: (IntVal 11,fromList [])
|
||||
putStrLn $ show $ WhileInterp.run (Op Plus (Val (IntVal 9)) (Val (IntVal 2)))
|
||||
-- Should be: (IntVal 1,fromList [])
|
||||
putStrLn $ show $ WhileInterp.run (If (Val (BoolVal True)) (Val (IntVal 1)) (Val (IntVal 2)))
|
||||
-- Should be: (IntVal 2,fromList [])
|
||||
putStrLn $ show $ WhileInterp.run (If (Val (BoolVal False)) (Val (IntVal 1)) (Val (IntVal 2)))
|
||||
-- Should be: (BoolVal False,fromList [])
|
||||
putStrLn $ show $ WhileInterp.run (While (Val (BoolVal False)) (Val (IntVal 42)))
|
||||
-- Should be: (IntVal 666,fromList [("X",IntVal 666)])
|
||||
putStrLn $ show $ WhileInterp.run (Sequence
|
||||
(Assign "X" (Val (IntVal 666)))
|
||||
(Var "X"))
|
||||
|
||||
main :: IO ()
|
||||
main = do
|
||||
testUnit
|
||||
-- Should be: fromList [("X",IntVal 0),("Y",IntVal 10)]
|
||||
putStrLn $ show $ WhileInterp.testProgram w_test
|
||||
-- Should be: fromList [("F",IntVal 2),("N",IntVal 0),("X",IntVal 1),("Z",IntVal 2)]
|
||||
putStrLn $ show $ WhileInterp.testProgram w_fact
|
||||
|
||||
BIN
hw2/while-semantics.pdf
Normal file
BIN
hw2/while-semantics.pdf
Normal file
Binary file not shown.
187
hw2/while-semantics.tex
Normal file
187
hw2/while-semantics.tex
Normal file
@@ -0,0 +1,187 @@
|
||||
\documentclass{article}
|
||||
|
||||
\usepackage{fullpage}
|
||||
\usepackage{listings}
|
||||
\usepackage{amsmath}
|
||||
\usepackage{amsthm}
|
||||
\usepackage{amssymb}
|
||||
%\usepackagen{url}
|
||||
\usepackage{float}
|
||||
\usepackage{paralist}
|
||||
|
||||
\floatstyle{boxed}
|
||||
\restylefloat{figure}
|
||||
|
||||
|
||||
|
||||
\newcommand{\rel}[1]{ \mbox{\sc [#1]} }
|
||||
|
||||
\title{Homework 2: Operational Semantics for WHILE}
|
||||
|
||||
\author{
|
||||
CS 252: Advanced Programming Languages \\
|
||||
Yuri Tatishchev \\
|
||||
San Jos\'{e} State University \\
|
||||
}
|
||||
\date{}
|
||||
|
||||
\begin{document}
|
||||
\maketitle
|
||||
|
||||
\section{Introduction}
|
||||
|
||||
For this assignment,
|
||||
you will implement the semantics for a small imperative language, named WHILE.
|
||||
|
||||
% Commands for formatting figure
|
||||
\newcommand{\mydefhead}[2]{\multicolumn{2}{l}{{#1}}&\mbox{\emph{#2}}\\}
|
||||
\newcommand{\mydefcase}[2]{\qquad\qquad& #1 &\mbox{#2}\\}
|
||||
|
||||
% Commands for language format
|
||||
\newcommand{\assign}[2]{#1~{:=}~#2}
|
||||
\newcommand{\ife}[3]{\mbox{\tt if}~{#1}~\mbox{\tt then}~{#2}~\mbox{\tt else}~{#3}}
|
||||
\newcommand{\whilee}[2]{\mbox{\tt while}~(#1)~#2}
|
||||
\newcommand{\true}{\mbox{\tt true}}
|
||||
\newcommand{\false}{\mbox{\tt false}}
|
||||
\newcommand{\note}[1]{\mbox{\tt not}~{#1}}
|
||||
|
||||
\begin{figure}[H]
|
||||
\caption{The WHILE language}
|
||||
\label{fig:lang}
|
||||
\[
|
||||
\begin{array}{llr}
|
||||
\mydefhead{e ::=\qquad\qquad\qquad\qquad}{Expressions}
|
||||
\mydefcase{x}{variables/addresses}
|
||||
\mydefcase{v}{values}
|
||||
\mydefcase{\assign x e}{assignment}
|
||||
\mydefcase{e; e}{sequential expressions}
|
||||
\mydefcase{e ~op~ e}{binary operations}
|
||||
\mydefcase{\ife e e e}{conditional expressions}
|
||||
\mydefcase{\whilee e e}{while expressions}
|
||||
\mydefcase{e ~boolop~ e}{boolean binary operations}
|
||||
\mydefcase{\note e}{negation}
|
||||
\\
|
||||
\mydefhead{v ::=\qquad\qquad\qquad\qquad}{Values}
|
||||
\mydefcase{i}{integer values}
|
||||
\mydefcase{b}{boolean values}
|
||||
\\
|
||||
op ::= & + ~|~ - ~|~ * ~|~ / ~|~ > ~|~ >= ~|~ < ~|~ <= & \mbox{\emph{Binary operators}} \\
|
||||
\\
|
||||
boolop ::= & and ~|~ or & \mbox{\emph{Boolean binary operators}} \\
|
||||
\end{array}
|
||||
\]
|
||||
\end{figure}
|
||||
|
||||
The language for WHILE is given in Figure~\ref{fig:lang}.
|
||||
Unlike the Bool* language we discussed previously,
|
||||
WHILE supports \emph{mutable references}.
|
||||
The state of these references is maintained in a \emph{store},
|
||||
a mapping of references to values.
|
||||
(``Store'' can be thought of as a synonym for heap.)
|
||||
Once we have mutable references, other language constructs become more useful,
|
||||
such as sequencing operations ($e_1;e_2$).
|
||||
|
||||
|
||||
%--------------
|
||||
\section{Semantics}
|
||||
\newcommand{\bstep}[4]{{#1},{#2} \Downarrow {#3},{#4}}
|
||||
|
||||
% Format for a big-step evaluation rule.
|
||||
% #1 is the name of the rule.
|
||||
% #2 are the premises. Leave blank if there are none.
|
||||
% #3 is the conclusion.
|
||||
\newcommand{\bsrule}[3]{
|
||||
\rel{#1} &
|
||||
\frac{\strut\begin{array}{@{}c@{}} #2 \end{array}}
|
||||
{\strut\begin{array}{@{}c@{}} #3 \end{array}}
|
||||
\\~\\
|
||||
}
|
||||
|
||||
\begin{figure}[H]
|
||||
\caption{Big-step semantics for WHILE}
|
||||
\label{fig:bigstep}
|
||||
{\bf Runtime Syntax:}
|
||||
\[
|
||||
\begin{array}{rclcl}
|
||||
\sigma & \in & {Store} \quad & = & \quad {variable} ~\rightarrow ~v \\
|
||||
\\
|
||||
\end{array}
|
||||
\]
|
||||
{\bf Evaluation Rules:~~~ \fbox{$\bstep{e}{\sigma}{e'}{\sigma'}$}} \\
|
||||
\[
|
||||
\begin{array}{r@{\qquad\qquad}c}
|
||||
\bsrule{B-Value}{}{
|
||||
\bstep{v}{\sigma}{v}{\sigma}
|
||||
}
|
||||
\bsrule{B-Var}{
|
||||
x \in domain(\sigma) \qquad \sigma(x)=v
|
||||
}{
|
||||
\bstep{x}{\sigma}{v}{\sigma}
|
||||
}
|
||||
\bsrule{B-Assign}{
|
||||
\bstep{e}{\sigma}{v}{\sigma'}
|
||||
}{
|
||||
\bstep{\assign{x}{e}}{\sigma}{v}{\sigma'[x:=v]}
|
||||
}
|
||||
\bsrule{B-Seq}{
|
||||
\bstep{e_1}{\sigma}{v_1}{\sigma'} \\
|
||||
\bstep{e_2}{\sigma'}{v_2}{\sigma''}
|
||||
}{
|
||||
\bstep{e_1;e_2}{\sigma}{v_2}{\sigma''}
|
||||
}
|
||||
\bsrule{B-Op}{
|
||||
\bstep{e_1}{\sigma}{v_1}{\sigma'} \\
|
||||
\bstep{e_2}{\sigma'}{v_2}{\sigma''} \\
|
||||
v = apply(op, v_1, v_2)
|
||||
}{
|
||||
\bstep{e_1~op~e_2}{\sigma}{v}{\sigma''}
|
||||
}
|
||||
\bsrule{B-IfTrue}{
|
||||
\bstep{e_1}{\sigma}{\true}{\sigma'} \\
|
||||
\bstep{e_2}{\sigma'}{v}{\sigma''}
|
||||
}{
|
||||
\bstep{\ife{e_1}{e_2}{e_3}}{\sigma}{v}{\sigma''}
|
||||
}
|
||||
\bsrule{B-IfFalse}{
|
||||
\bstep{e_1}{\sigma}{\false}{\sigma'} \\
|
||||
\bstep{e_3}{\sigma'}{v}{\sigma''}
|
||||
}{
|
||||
\bstep{\ife{e_1}{e_2}{e_3}}{\sigma}{v}{\sigma''}
|
||||
}
|
||||
\bsrule{B-WhileTrue}{
|
||||
\bstep{e_1}{\sigma}{\true}{\sigma'} \\
|
||||
\bstep{e_2}{\sigma'}{v_1}{\sigma''} \\
|
||||
\bstep{\whilee{e_1}{e_2}}{\sigma''}{v}{\sigma'''}
|
||||
}{
|
||||
\bstep{\whilee{e_1}{e_2}}{\sigma}{v}{\sigma'''}
|
||||
}
|
||||
\bsrule{B-WhileFalse}{
|
||||
\bstep{e_1}{\sigma}{\false}{\sigma'}
|
||||
}{
|
||||
\bstep{\whilee{e_1}{e_2}}{\sigma}{\false}{\sigma'}
|
||||
}
|
||||
% \bsrule{B-While}{
|
||||
% \bstep{e_1}{\sigma}{v_1}{\sigma'} \\
|
||||
% \bstep{e_2}{\sigma'}{v_2}{\sigma''}
|
||||
% }{
|
||||
% \bstep{\whilee{e_1}{e_2}}{\sigma}{\ife{v_1}{e_2;\whilee{e_1}{e_2}}{\false}}{\sigma''}
|
||||
% }
|
||||
\bsrule{B-Not}{
|
||||
\bstep{e}{\sigma}{b}{\sigma'}
|
||||
}{
|
||||
\bstep{\note{e}}{\sigma}{\neg b}{\sigma'}
|
||||
}
|
||||
\bsrule{B-BoolOp}{
|
||||
\bstep{e_1}{\sigma}{b_1}{\sigma'} \\
|
||||
\bstep{e_2}{\sigma'}{b_2}{\sigma''} \\
|
||||
b = apply(boolop, b_1, b_2)
|
||||
}{
|
||||
\bstep{e_1~boolop~e_2}{\sigma}{b}{\sigma''}
|
||||
}
|
||||
\end{array}
|
||||
\]
|
||||
\end{figure}
|
||||
|
||||
|
||||
\end{document}
|
||||
|
||||
251
hw3/WhileInterp.hs
Normal file
251
hw3/WhileInterp.hs
Normal file
@@ -0,0 +1,251 @@
|
||||
{-
|
||||
Name: Yuri Tatishchev
|
||||
Class: CS 252
|
||||
Assigment: HW3
|
||||
Date: 2026-03-27
|
||||
Description: Parser/interpreter for the WHILE language
|
||||
-}
|
||||
|
||||
|
||||
module WhileInterp (
|
||||
Expression(..),
|
||||
Binop(..),
|
||||
Value(..),
|
||||
runFile,
|
||||
showParsedExp,
|
||||
run
|
||||
) where
|
||||
|
||||
import Data.Map (Map)
|
||||
import qualified Data.Map as Map
|
||||
import Text.ParserCombinators.Parsec
|
||||
import Control.Monad.Except
|
||||
|
||||
-- We represent variables as strings.
|
||||
type Variable = String
|
||||
|
||||
--We also represent error messages as strings.
|
||||
type ErrorMsg = String
|
||||
|
||||
-- The store is an associative map from variables to values.
|
||||
-- (The store roughly corresponds with the heap in a language like Java).
|
||||
type Store = Map Variable Value
|
||||
|
||||
data Expression =
|
||||
Var Variable -- x
|
||||
| Val Value -- v
|
||||
| Assign Variable Expression -- x := e
|
||||
| Sequence Expression Expression -- e1; e2
|
||||
| Op Binop Expression Expression
|
||||
| If Expression Expression Expression -- if e1 then e2 else e3 endif
|
||||
| While Expression Expression -- while e1 do e2 endwhile
|
||||
deriving (Show)
|
||||
|
||||
data Binop =
|
||||
Plus -- + :: Int -> Int -> Int
|
||||
| Minus -- - :: Int -> Int -> Int
|
||||
| Times -- * :: Int -> Int -> Int
|
||||
| Divide -- / :: Int -> Int -> Int
|
||||
| Gt -- > :: Int -> Int -> Bool
|
||||
| Ge -- >= :: Int -> Int -> Bool
|
||||
| Lt -- < :: Int -> Int -> Bool
|
||||
| Le -- <= :: Int -> Int -> Bool
|
||||
deriving (Show)
|
||||
|
||||
data Value =
|
||||
IntVal Int
|
||||
| BoolVal Bool
|
||||
deriving (Show)
|
||||
|
||||
|
||||
fileP :: GenParser Char st Expression
|
||||
fileP = do
|
||||
prog <- exprP
|
||||
eof
|
||||
return prog
|
||||
|
||||
exprP = do
|
||||
e <- exprP'
|
||||
rest <- optionMaybe restSeqP
|
||||
return (case rest of
|
||||
Nothing -> e
|
||||
Just e' -> Sequence e e')
|
||||
|
||||
-- Expressions are divided into terms and expressions for the sake of
|
||||
-- parsing. Note that binary operators **DO NOT** follow the expected
|
||||
-- presidence rules.
|
||||
--
|
||||
-- ***FOR 2pts EXTRA CREDIT (hard, no partial credit)***
|
||||
-- Correct the precedence of the binary operators.
|
||||
exprP' = do
|
||||
spaces
|
||||
t <- termP
|
||||
spaces
|
||||
rest <- optionMaybe restP
|
||||
spaces
|
||||
return (case rest of
|
||||
Nothing -> t
|
||||
Just (":=", t') -> (case t of
|
||||
Var varName -> Assign varName t'
|
||||
_ -> error "Expected var")
|
||||
Just (op, t') -> Op (transOp op) t t')
|
||||
|
||||
restSeqP = do
|
||||
char ';'
|
||||
exprP
|
||||
|
||||
transOp s = case s of
|
||||
"+" -> Plus
|
||||
"-" -> Minus
|
||||
"*" -> Times
|
||||
"/" -> Divide
|
||||
">=" -> Ge
|
||||
">" -> Gt
|
||||
"<=" -> Le
|
||||
"<" -> Lt
|
||||
o -> error $ "Unexpected operator " ++ o
|
||||
|
||||
-- Some string, followed by an expression
|
||||
restP = do
|
||||
ch <- string "+"
|
||||
<|> string "-"
|
||||
<|> string "*"
|
||||
<|> string "/"
|
||||
<|> try (string "<=")
|
||||
<|> string "<"
|
||||
<|> try (string ">=")
|
||||
<|> string ">"
|
||||
<|> string ":=" -- not really a binary operator, but it fits in nicely here.
|
||||
<?> "binary operator"
|
||||
e <- exprP'
|
||||
return (ch, e)
|
||||
|
||||
-- All terms can be distinguished by looking at the first character
|
||||
termP = valP
|
||||
<|> ifP
|
||||
<|> whileP
|
||||
<|> parenP
|
||||
<|> varP
|
||||
<?> "value, variable, 'if', 'while', or '('"
|
||||
|
||||
|
||||
valP = do
|
||||
v <- boolP <|> numberP
|
||||
return $ Val v
|
||||
|
||||
boolP = do
|
||||
bStr <- string "true" <|> string "false" <|> string "skip"
|
||||
return $ case bStr of
|
||||
"true" -> BoolVal True
|
||||
"false" -> BoolVal False
|
||||
"skip" -> BoolVal False -- Treating the command 'skip' as a synonym for false, for ease of parsing
|
||||
|
||||
numberP = do
|
||||
n <- many1 digit
|
||||
return $ IntVal (read n)
|
||||
|
||||
varP = do
|
||||
x <- many1 letter
|
||||
return $ Var x
|
||||
|
||||
ifP = do
|
||||
string "if"
|
||||
e1 <- exprP
|
||||
string "then"
|
||||
e2 <- exprP
|
||||
string "else"
|
||||
e3 <- exprP
|
||||
string "endif"
|
||||
return $ If e1 e2 e3
|
||||
|
||||
whileP = do
|
||||
string "while"
|
||||
e1 <- exprP
|
||||
string "do"
|
||||
e2 <- exprP
|
||||
string "endwhile"
|
||||
return $ While e1 e2
|
||||
|
||||
-- An expression in parens, e.g. (9-5)*2
|
||||
parenP = do
|
||||
char '('
|
||||
e <- exprP
|
||||
char ')'
|
||||
return $ e
|
||||
|
||||
|
||||
-- This function will be useful for defining binary operations.
|
||||
-- Unlike in the previous assignment, this function returns an "Either value".
|
||||
-- The right side represents a successful computaton.
|
||||
-- The left side is an error message indicating a problem with the program.
|
||||
-- The first case is done for you.
|
||||
applyOp :: Binop -> Value -> Value -> Either ErrorMsg Value
|
||||
applyOp Plus (IntVal i) (IntVal j) = Right $ IntVal $ i + j
|
||||
applyOp Minus (IntVal i) (IntVal j) = Right $ IntVal $ i - j
|
||||
applyOp Times (IntVal i) (IntVal j) = Right $ IntVal $ i * j
|
||||
applyOp Divide (IntVal i) (IntVal j) = Right $ IntVal $ i `div` j
|
||||
applyOp Gt (IntVal i) (IntVal j) = Right $ BoolVal $ i > j
|
||||
applyOp Ge (IntVal i) (IntVal j) = Right $ BoolVal $ i >= j
|
||||
applyOp Lt (IntVal i) (IntVal j) = Right $ BoolVal $ i < j
|
||||
applyOp Le (IntVal i) (IntVal j) = Right $ BoolVal $ i <= j
|
||||
applyOp _ v1 v2 = Left $ "Non-integer value pair: '" ++ show v1 ++ "', '" ++ show v2 ++ "' used in binary operation"
|
||||
|
||||
|
||||
-- As with the applyOp method, the semantics for this function
|
||||
-- should return Either values. Left <error msg> indicates an error,
|
||||
-- whereas Right <something> indicates a successful execution.
|
||||
evaluate :: Expression -> Store -> Either ErrorMsg (Value, Store)
|
||||
evaluate (Val v) s = Right (v, s)
|
||||
evaluate (Var x) s = case Map.lookup x s of
|
||||
Just v -> Right (v, s)
|
||||
Nothing -> Left $ "Variable " ++ x ++ " not found"
|
||||
evaluate (Assign x e) s = do
|
||||
(v,s') <- evaluate e s
|
||||
return (v, Map.insert x v s')
|
||||
evaluate (Sequence e1 e2) s = do
|
||||
(_,s1) <- evaluate e1 s
|
||||
(v2,s') <- evaluate e2 s1
|
||||
return (v2, s')
|
||||
evaluate (Op o e1 e2) s = do
|
||||
(v1,s1) <- evaluate e1 s
|
||||
(v2,s') <- evaluate e2 s1
|
||||
v <- applyOp o v1 v2
|
||||
return (v, s')
|
||||
evaluate (If e1 e2 e3) s = do
|
||||
(v1, s1) <- evaluate e1 s
|
||||
(v', s') <- case v1 of
|
||||
BoolVal True -> evaluate e2 s1
|
||||
BoolVal False -> evaluate e3 s1
|
||||
_ -> Left $ "Non-boolean value '" ++ show v1 ++ "' used as conditional"
|
||||
return (v', s')
|
||||
evaluate (While e1 e2) s = do
|
||||
(v1,s1) <- evaluate e1 s
|
||||
case v1 of
|
||||
BoolVal True -> do
|
||||
(_,s2) <- evaluate e2 s1
|
||||
(v', s') <- evaluate (While e1 e2) s2
|
||||
return (v', s')
|
||||
BoolVal False -> Right $ (BoolVal False, s)
|
||||
_ -> Left $ "Non-boolean value '" ++ show v1 ++ "' used as while condition"
|
||||
|
||||
|
||||
-- Evaluates a program with an initially empty state
|
||||
run :: Expression -> Either ErrorMsg (Value, Store)
|
||||
run prog = evaluate prog Map.empty
|
||||
|
||||
showParsedExp fileName = do
|
||||
p <- parseFromFile fileP fileName
|
||||
case p of
|
||||
Left parseErr -> print parseErr
|
||||
Right exp -> print exp
|
||||
|
||||
runFile fileName = do
|
||||
p <- parseFromFile fileP fileName
|
||||
case p of
|
||||
Left parseErr -> print parseErr
|
||||
Right exp ->
|
||||
case (run exp) of
|
||||
Left msg -> print msg
|
||||
Right (v,s) -> print $ show s
|
||||
|
||||
|
||||
6
hw3/abs.imp
Normal file
6
hw3/abs.imp
Normal file
@@ -0,0 +1,6 @@
|
||||
X := 0 - 3;
|
||||
if X < 0 then
|
||||
X := 0 - X
|
||||
else
|
||||
skip
|
||||
endif
|
||||
7
hw3/error.imp
Normal file
7
hw3/error.imp
Normal file
@@ -0,0 +1,7 @@
|
||||
x := 7 + 3;
|
||||
if x then
|
||||
true
|
||||
else
|
||||
false
|
||||
endif
|
||||
|
||||
4
hw3/extra.imp
Normal file
4
hw3/extra.imp
Normal file
@@ -0,0 +1,4 @@
|
||||
X := 3 + 9 * 4;
|
||||
Y := 5 * 2 + 7;
|
||||
Z := 360 / 12 / 3
|
||||
|
||||
11
hw3/fact.imp
Normal file
11
hw3/fact.imp
Normal file
@@ -0,0 +1,11 @@
|
||||
N := 2;
|
||||
F := 1;
|
||||
while N > 0 do
|
||||
X := N;
|
||||
Z := F;
|
||||
while X > 1 do
|
||||
F := Z + F;
|
||||
X := X - 1
|
||||
endwhile;
|
||||
N := N - 1
|
||||
endwhile
|
||||
20
hw3/output.txt
Normal file
20
hw3/output.txt
Normal file
@@ -0,0 +1,20 @@
|
||||
***Testing abs.imp
|
||||
Sequence (Assign "X" (Op Minus (Val (IntVal 0)) (Val (IntVal 3)))) (If (Op Lt (Var "X") (Val (IntVal 0))) (Assign "X" (Op Minus (Val (IntVal 0)) (Var "X"))) (Val (BoolVal False)))
|
||||
"fromList [(\"X\",IntVal 3)]"
|
||||
|
||||
***Testing fact.imp
|
||||
Sequence (Assign "N" (Val (IntVal 2))) (Sequence (Assign "F" (Val (IntVal 1))) (While (Op Gt (Var "N") (Val (IntVal 0))) (Sequence (Assign "X" (Var "N")) (Sequence (Assign "Z" (Var "F")) (Sequence (While (Op Gt (Var "X") (Val (IntVal 1))) (Sequence (Assign "F" (Op Plus (Var "Z") (Var "F"))) (Assign "X" (Op Minus (Var "X") (Val (IntVal 1)))))) (Assign "N" (Op Minus (Var "N") (Val (IntVal 1)))))))))
|
||||
"fromList [(\"F\",IntVal 2),(\"N\",IntVal 0),(\"X\",IntVal 1),(\"Z\",IntVal 2)]"
|
||||
|
||||
***Testing times.imp
|
||||
Sequence (Assign "X" (Val (IntVal 10))) (Sequence (Assign "Y" (Val (IntVal 3))) (Sequence (Assign "Z" (Val (IntVal 0))) (While (Op Gt (Var "X") (Val (IntVal 0))) (Sequence (Assign "Z" (Op Plus (Var "Z") (Var "Y"))) (Assign "X" (Op Minus (Var "X") (Val (IntVal 1))))))))
|
||||
"fromList [(\"X\",IntVal 0),(\"Y\",IntVal 3),(\"Z\",IntVal 30)]"
|
||||
|
||||
***Testing test.imp
|
||||
Sequence (Assign "X" (Op Plus (Op Minus (Op Plus (Val (IntVal 1)) (Val (IntVal 2))) (Val (IntVal 3))) (Op Plus (Val (IntVal 1)) (Val (IntVal 3))))) (Sequence (Assign "Y" (Val (IntVal 0))) (While (Op Gt (Var "X") (Val (IntVal 0))) (Sequence (Assign "Y" (Op Plus (Var "Y") (Var "X"))) (Assign "X" (Op Minus (Var "X") (Val (IntVal 1)))))))
|
||||
"fromList [(\"X\",IntVal 0),(\"Y\",IntVal 10)]"
|
||||
|
||||
***Testing error.imp
|
||||
Sequence (Assign "x" (Op Plus (Val (IntVal 7)) (Val (IntVal 3)))) (If (Var "x") (Val (BoolVal True)) (Val (BoolVal False)))
|
||||
"Non-boolean value 'IntVal 10' used as conditional"
|
||||
|
||||
19
hw3/output_expected.txt
Normal file
19
hw3/output_expected.txt
Normal file
@@ -0,0 +1,19 @@
|
||||
***Testing abs.imp
|
||||
Sequence (Assign "X" (Op Minus (Val (IntVal 0)) (Val (IntVal 3)))) (If (Op Lt (Var "X") (Val (IntVal 0))) (Assign "X" (Op Minus (Val (IntVal 0)) (Var "X"))) (Val (BoolVal False)))
|
||||
"fromList [(\"X\",IntVal 3)]"
|
||||
|
||||
***Testing fact.imp
|
||||
Sequence (Assign "N" (Val (IntVal 2))) (Sequence (Assign "F" (Val (IntVal 1))) (While (Op Gt (Var "N") (Val (IntVal 0))) (Sequence (Assign "X" (Var "N")) (Sequence (Assign "Z" (Var "F")) (Sequence (While (Op Gt (Var "X") (Val (IntVal 1))) (Sequence (Assign "F" (Op Plus (Var "Z") (Var "F"))) (Assign "X" (Op Minus (Var "X") (Val (IntVal 1)))))) (Assign "N" (Op Minus (Var "N") (Val (IntVal 1)))))))))
|
||||
"fromList [(\"F\",IntVal 2),(\"N\",IntVal 0),(\"X\",IntVal 1),(\"Z\",IntVal 2)]"
|
||||
|
||||
***Testing times.imp
|
||||
Sequence (Assign "X" (Val (IntVal 10))) (Sequence (Assign "Y" (Val (IntVal 3))) (Sequence (Assign "Z" (Val (IntVal 0))) (While (Op Gt (Var "X") (Val (IntVal 0))) (Sequence (Assign "Z" (Op Plus (Var "Z") (Var "Y"))) (Assign "X" (Op Minus (Var "X") (Val (IntVal 1))))))))
|
||||
"fromList [(\"X\",IntVal 0),(\"Y\",IntVal 3),(\"Z\",IntVal 30)]"
|
||||
|
||||
***Testing test.imp
|
||||
Sequence (Assign "X" (Op Plus (Op Minus (Op Plus (Val (IntVal 1)) (Val (IntVal 2))) (Val (IntVal 3))) (Op Plus (Val (IntVal 1)) (Val (IntVal 3))))) (Sequence (Assign "Y" (Val (IntVal 0))) (While (Op Gt (Var "X") (Val (IntVal 0))) (Sequence (Assign "Y" (Op Plus (Var "Y") (Var "X"))) (Assign "X" (Op Minus (Var "X") (Val (IntVal 1)))))))
|
||||
"fromList [(\"X\",IntVal 0),(\"Y\",IntVal 10)]"
|
||||
|
||||
***Testing error.imp
|
||||
Sequence (Assign "x" (Op Plus (Val (IntVal 7)) (Val (IntVal 3)))) (If (Var "x") (Val (BoolVal True)) (Val (BoolVal False)))
|
||||
test.hs: Non-boolean value '10' used as a conditional
|
||||
17
hw3/test.hs
Normal file
17
hw3/test.hs
Normal file
@@ -0,0 +1,17 @@
|
||||
import WhileInterp
|
||||
|
||||
test f = do
|
||||
putStrLn $ "***Testing " ++ f
|
||||
showParsedExp f
|
||||
runFile f
|
||||
putStrLn ""
|
||||
|
||||
main :: IO ()
|
||||
main = do
|
||||
test "abs.imp"
|
||||
--test "extra.imp"
|
||||
test "fact.imp"
|
||||
test "times.imp"
|
||||
test "test.imp"
|
||||
test "error.imp"
|
||||
|
||||
6
hw3/test.imp
Normal file
6
hw3/test.imp
Normal file
@@ -0,0 +1,6 @@
|
||||
X := ( ( 1 + 2 ) - 3 ) + ( 1 + 3 );
|
||||
Y := 0;
|
||||
while X>0 do
|
||||
Y := Y+X;
|
||||
X := X-1
|
||||
endwhile
|
||||
7
hw3/times.imp
Normal file
7
hw3/times.imp
Normal file
@@ -0,0 +1,7 @@
|
||||
X := 10;
|
||||
Y := 3;
|
||||
Z := 0;
|
||||
while X > 0 do
|
||||
Z := Z + Y;
|
||||
X := X - 1
|
||||
endwhile
|
||||
@@ -6,28 +6,33 @@ First, implement your own version of the foldl function,
|
||||
defined as myFoldl
|
||||
|
||||
> myFoldl :: (a -> b -> a) -> a -> [b] -> a
|
||||
> myFoldl _ _ _ = error "TBD"
|
||||
> myFoldl f acc [] = acc
|
||||
> myFoldl f acc (x:xs) = myFoldl f (f acc x) xs
|
||||
|
||||
|
||||
Next, define a function to reverse a list using foldl.
|
||||
|
||||
> myReverse :: [a] -> [a]
|
||||
> myReverse _ = error "TBD"
|
||||
> myReverse = foldl (\acc x -> x : acc) []
|
||||
|
||||
|
||||
Now define your own version of foldr, named myFoldr
|
||||
|
||||
> myFoldr :: (a -> b -> b) -> b -> [a] -> b
|
||||
> myFoldr _ _ _ = error "TBD"
|
||||
> myFoldr f acc [] = acc
|
||||
> myFoldr f acc (x:xs) = f x $ myFoldr f acc xs
|
||||
|
||||
|
||||
Now try using foldl (the library version, not yours) to sum up the numbers of a large list.
|
||||
Why is it so slow?
|
||||
|
||||
foldl is slow because it repeatedly pushes unevaluated expressions on the stack
|
||||
|
||||
Instead of foldl, try using foldl'.
|
||||
Why is it faster?
|
||||
(Read http://www.haskell.org/haskellwiki/Foldr_Foldl_Foldl%27 for some hints)
|
||||
|
||||
foldl' evaluates the accumulator at each step, preventing the build up of unevaluated expressions
|
||||
|
||||
For an extra challenge, try to implement foldl in terms of foldr.
|
||||
See http://www.haskell.org/haskellwiki/Foldl_as_foldr for details.
|
||||
@@ -36,11 +41,20 @@ See http://www.haskell.org/haskellwiki/Foldl_as_foldr for details.
|
||||
Next, using the map function, convert every item in a list to its absolute value
|
||||
|
||||
> listAbs :: [Integer] -> [Integer]
|
||||
> listAbs _ = error "TBD"
|
||||
> listAbs = map abs
|
||||
|
||||
Finally, write a function that takes a list of Integers and returns the sum of
|
||||
their absolute values.
|
||||
|
||||
> sumAbs :: [Integer] -> Integer
|
||||
> sumAbs _ = error "TBD"
|
||||
> sumAbs = sum . listAbs
|
||||
|
||||
> main :: IO ()
|
||||
> main = do
|
||||
> putStrLn $ "myFoldl (+) 0 [1..10] = " ++ show (myFoldl (+) 0 [1..10])
|
||||
> putStrLn $ "myReverse [1..5] = " ++ show (myReverse [1..5])
|
||||
> putStrLn $ "myFoldr (+) 0 [1..10] = " ++ show (myFoldr (+) 0 [1..10])
|
||||
> putStrLn $ "listAbs [-1, -2, 3, -4] = " ++ show (listAbs [-1, -2, 3, -4])
|
||||
> putStrLn $ "sumAbs [-1, -2, 3, -4] = " ++ show (sumAbs [-1, -2, 3, -4])
|
||||
> putStrLn $ "foldl (+) 0 [1..10000000] = " ++ show (foldl (+) 0 [1..10000000])
|
||||
> putStrLn $ "foldl' (+) 0 [1..10000000] = " ++ show (foldl' (+) 0 [1..10000000])
|
||||
|
||||
25
lab05/maybeEither.hs
Normal file
25
lab05/maybeEither.hs
Normal file
@@ -0,0 +1,25 @@
|
||||
getMax :: [Int] -> Maybe Int
|
||||
getMax [] = Nothing
|
||||
getMax x = Just (maximum x)
|
||||
|
||||
reciprocal :: (Eq a, Fractional a) => a -> Maybe a
|
||||
reciprocal 0 = Nothing
|
||||
reciprocal x = Just (1/x)
|
||||
|
||||
rectangleArea :: Int -> Int -> Either String Int
|
||||
rectangleArea x y
|
||||
| x < 0 = Left "Width is not positive"
|
||||
| y < 0 = Left "Height is not positive"
|
||||
| otherwise = Right (x * y)
|
||||
|
||||
main :: IO ()
|
||||
main = do
|
||||
print $ getMax []
|
||||
print $ getMax [99,12,37]
|
||||
print $ getMax [-99,-12,-37]
|
||||
print $ reciprocal 4
|
||||
print $ reciprocal 2
|
||||
print $ reciprocal 0
|
||||
print $ rectangleArea 5 10
|
||||
print $ rectangleArea (-5) 10
|
||||
print $ rectangleArea 5 (-10)
|
||||
BIN
lab05/maybeEither.signed.zip
Normal file
BIN
lab05/maybeEither.signed.zip
Normal file
Binary file not shown.
24
lab06/functors.lhs
Normal file
24
lab06/functors.lhs
Normal file
@@ -0,0 +1,24 @@
|
||||
> data Tree v =
|
||||
> Empty
|
||||
> | Node v (Tree v) (Tree v)
|
||||
> deriving (Show)
|
||||
|
||||
> instance Functor Tree where
|
||||
> fmap f (Node v left right) = Node (f v) (fmap f left) (fmap f right)
|
||||
> fmap f Empty = Empty
|
||||
|
||||
The findT method shows how we may search through the tree to find a value.
|
||||
|
||||
> findT :: Ord v => v -> Tree v -> Maybe v
|
||||
> findT _ Empty = Nothing
|
||||
> findT v (Node val left right) =
|
||||
> if val == v then
|
||||
> Just val
|
||||
> else if v < val then
|
||||
> findT v left
|
||||
> else
|
||||
> findT v right
|
||||
|
||||
Your job is to add support for fmap to this tree, so that the call to fmap below works:
|
||||
|
||||
> main = print $ fmap (+1) (Node 3 (Node 1 Empty Empty) (Node 7 (Node 4 Empty Empty) Empty))
|
||||
89
lab07/facetedValues.lhs
Normal file
89
lab07/facetedValues.lhs
Normal file
@@ -0,0 +1,89 @@
|
||||
> import Control.Applicative
|
||||
> import qualified Data.Set as Set
|
||||
|
||||
Faceted values are an **information flow mechanism**. Specifically, they are
|
||||
designed to store differing views of data. For more technical details, see
|
||||
"Multiple Facets for Dynamic Information Flow", available at
|
||||
https://users.soe.ucsc.edu/~cormac/papers/popl12b.pdf.
|
||||
|
||||
Authorized viewers should see the real value, and other viewers should see
|
||||
dummy data instead. To do this, we need to represent the security level
|
||||
of a piece of data. We'll define labels to encapsulate this information,
|
||||
represented as strings.
|
||||
|
||||
> type Label = String
|
||||
|
||||
A user may have many security privileges, so a "view" of a faceted value can
|
||||
be represented as a set of labels.
|
||||
|
||||
> type View = Set.Set Label
|
||||
|
||||
A faceted value can be either a raw (that is, unfaceted) value, or it can
|
||||
be node containing two nested faceted values, with a label tracking who is
|
||||
allowed to view the contents.
|
||||
|
||||
> data FacetedValue a = Raw a
|
||||
> | Facet Label (FacetedValue a) (FacetedValue a)
|
||||
|
||||
Note that we do **not** derive Show. Instead, code must pass its
|
||||
authorizations to a view function, which will return a non-faceted value.
|
||||
If any label is not in the set, it is assumed that the view is not authorized.
|
||||
|
||||
> view :: View -> FacetedValue a -> a
|
||||
> view _ (Raw x) = x
|
||||
> view labels (Facet k auth unauth) =
|
||||
> if Set.member k labels then
|
||||
> view labels auth
|
||||
> else
|
||||
> view labels unauth
|
||||
|
||||
We can apply operations to a faceted value, in which case the action should
|
||||
be applied to every element of the tree. As a review of the last lab, define
|
||||
fmap for FacetedValues.
|
||||
|
||||
> instance Functor FacetedValue where
|
||||
> fmap f (Raw x) = Raw (f x)
|
||||
> fmap f (Facet label auth unauth) = Facet label (fmap f auth) (fmap f unauth)
|
||||
|
||||
|
||||
The following function gives an example of how a FacetedValue can be used.
|
||||
In this case, a customer's Visa credit card is hidden from other viewers.
|
||||
If someone with other permissions, say with the ability to view details
|
||||
about the customer's Mastercard, they will instead be presented with the
|
||||
default view of 0. Even if someone tries to do some calculations on the
|
||||
credit card in the hope of revealing information, a consistent view will be
|
||||
presented to the observer.
|
||||
|
||||
> testFmap = do
|
||||
> let creditCard = Facet "visa" (Raw 4111111111111111) (Raw 0)
|
||||
> ccPlusOne = fmap (+1) creditCard
|
||||
> putStrLn "Credit card views:"
|
||||
> print $ view (Set.fromList ["visa"]) ccPlusOne -- Should print 4111111111111112
|
||||
> print $ view (Set.fromList ["mastercard"]) ccPlusOne -- Should print 1
|
||||
|
||||
|
||||
|
||||
While this works, what happens if we want two faceted values to be combined?
|
||||
In order to make that work, we need need to add support for Applicative Functors.
|
||||
Define the behavior of the Functor below
|
||||
|
||||
> instance Applicative FacetedValue where
|
||||
> pure = Raw
|
||||
> Raw f <*> fv2 = fmap f fv2
|
||||
> Facet label auth unauth <*> fv2 = Facet label (auth <*> fv2) (unauth <*> fv2)
|
||||
|
||||
|
||||
The code below gives an example of how this might come up. If code authorized
|
||||
to read your Bank of America account details runs on the same machine as code
|
||||
to read your Wells Fargo details, **neither** will be allowed to read your
|
||||
combined balance. However, you as the customer can see the true values.
|
||||
|
||||
> testApplicative = do
|
||||
> let bofaBalance = Facet "Bank of America" (Raw 44) (Raw 0)
|
||||
> wellsFargoBalance = Facet "Wells Fargo" (Raw 122) (Raw 0)
|
||||
> combinedBalance = (+) <$> bofaBalance <*> wellsFargoBalance
|
||||
> print $ view (Set.fromList []) combinedBalance -- Should print 0
|
||||
> print $ view (Set.fromList ["Bank of America"]) combinedBalance -- Should print 44
|
||||
> print $ view (Set.fromList ["Wells Fargo"]) combinedBalance -- Should print 122
|
||||
> print $ view (Set.fromList ["Bank of America", "Wells Fargo"]) combinedBalance -- Should print 166
|
||||
|
||||
8
lab08/applyMaybe.hs
Normal file
8
lab08/applyMaybe.hs
Normal file
@@ -0,0 +1,8 @@
|
||||
applyMaybe :: Maybe a -> (a -> Maybe b) -> Maybe b
|
||||
applyMaybe Nothing f = Nothing
|
||||
applyMaybe (Just x) f = f x
|
||||
|
||||
test1 = Just 3 `applyMaybe` (\x -> Just $ x * 2) `applyMaybe` (\x -> Just $ x - 1)
|
||||
|
||||
test2 = Just 3 `applyMaybe` (\_ -> Nothing) `applyMaybe` (\x -> Just $ x - 1)
|
||||
|
||||
15
lab08/bender.hs
Normal file
15
lab08/bender.hs
Normal file
@@ -0,0 +1,15 @@
|
||||
type Pos = (Int, Int)
|
||||
|
||||
start = (0,0)
|
||||
|
||||
up (x, y) = (x, y+1)
|
||||
down (x, y) = (x, y-1)
|
||||
left (x, y) = (x-1, y)
|
||||
right (x, y) = (x+1, y)
|
||||
|
||||
x -: f = f x
|
||||
|
||||
-- Using the "-:" operator, we can chain movements together
|
||||
test1 = start -: up -: right
|
||||
test2 = start -: up -: left -: left -: right -: down
|
||||
|
||||
34
lab08/benderPerhaps.hs
Normal file
34
lab08/benderPerhaps.hs
Normal file
@@ -0,0 +1,34 @@
|
||||
import Data.Map (Map)
|
||||
import qualified Data.Map as Map
|
||||
|
||||
-- In this code, we model bender moving around, but
|
||||
-- if he finds beer, he will stop responding to commands.
|
||||
type Pos = (Integer, Integer)
|
||||
|
||||
x -: f = f x
|
||||
|
||||
start = (0,0)
|
||||
|
||||
badPos = Map.empty
|
||||
-: Map.insert (0,2) True
|
||||
-: Map.insert (-1,3) True
|
||||
-: Map.insert (-3,-8) True
|
||||
|
||||
|
||||
moveTo :: Pos -> Maybe Pos
|
||||
moveTo p =
|
||||
if Map.member p badPos
|
||||
then Nothing
|
||||
else Just p
|
||||
|
||||
up (x, y) = moveTo (x, y+1)
|
||||
down (x, y) = moveTo (x, y-1)
|
||||
left (x, y) = moveTo (x-1, y)
|
||||
right (x, y) = moveTo (x+1, y)
|
||||
|
||||
-- Our directions now result in Maybe Pos values, so we can't chain them with "-:" anymore.
|
||||
test1 = return start >>= up >>= right
|
||||
test2 = return start >>= up >>= left >>= left >>= right >>= down
|
||||
test3 = return start >>= left >>= left >>= up >>= up >>= right >>= up >>= right >>= right >>= down
|
||||
|
||||
|
||||
21
lab08/doit.hs
Normal file
21
lab08/doit.hs
Normal file
@@ -0,0 +1,21 @@
|
||||
mydiv x y =
|
||||
x >>= (\numer ->
|
||||
y >>= (\denom ->
|
||||
if denom > 0
|
||||
then Just $ numer `div` denom
|
||||
else Nothing))
|
||||
|
||||
mydiv' x y = do
|
||||
numer <- x
|
||||
denom <- y
|
||||
if denom > 0
|
||||
then return $ numer `div` denom
|
||||
else Nothing
|
||||
|
||||
test1 = (Just 99) `mydiv` (Just 11)
|
||||
test1' = (Just 99) `mydiv'` (Just 11)
|
||||
|
||||
test2 = (Just 9) `mydiv` (Just 0)
|
||||
test2' = (Just 9) `mydiv'` (Just 0)
|
||||
|
||||
|
||||
120
lab08/monadLab.lhs
Normal file
120
lab08/monadLab.lhs
Normal file
@@ -0,0 +1,120 @@
|
||||
Below we have some mathematical binary arguments that you may recognize from homework 2.
|
||||
|
||||
> data Binop =
|
||||
> Plus -- + :: Int -> Int -> Int
|
||||
> | Minus -- - :: Int -> Int -> Int
|
||||
> | Times -- * :: Int -> Int -> Int
|
||||
> | Divide -- / :: Int -> Int -> Int
|
||||
> deriving (Show)
|
||||
|
||||
applyOp performs these operations, but unlike in the homework,
|
||||
you now must consider errors (represented by 'Nothing').
|
||||
|
||||
> applyOp :: Binop -> Maybe Int -> Maybe Int -> Maybe Int
|
||||
|
||||
Plus is done for you. Notice how code must check for 'Nothing'
|
||||
for each operand.
|
||||
|
||||
> applyOp Plus mi mj =
|
||||
> case mi of
|
||||
> Nothing -> Nothing
|
||||
> Just i ->
|
||||
> case mj of
|
||||
> Nothing -> Nothing
|
||||
> Just j -> Just $ i + j
|
||||
|
||||
Minus is also done for you. This case **does** use monads,
|
||||
but without the do syntax.
|
||||
|
||||
> applyOp Minus mi mj =
|
||||
> mi >>= (\i -> mj >>= (\j -> Just $ i - j))
|
||||
|
||||
Implement Times and Divide. Try the Times case without monads (as we did with
|
||||
the Plus case).
|
||||
|
||||
> applyOp Times mi mj =
|
||||
> case mi of
|
||||
> Nothing -> Nothing
|
||||
> Just i ->
|
||||
> case mj of
|
||||
> Nothing -> Nothing
|
||||
> Just j -> Just $ i * j
|
||||
|
||||
For the Divide case, use bind (>>=) as we did for Minus.
|
||||
On an attempt to divide by 0, return Nothing as the answer.
|
||||
|
||||
> applyOp Divide mi mj =
|
||||
> mi >>= (\i -> mj >>= (\j -> if j == 0 then Nothing else Just $ i `div` j))
|
||||
|
||||
The following test cases will help you verify your changes.
|
||||
|
||||
> testapp1 = applyOp Minus (applyOp Times (Just 3) (Just 4)) $ applyOp Divide (Just 8) (Just 2)
|
||||
> testapp2 = applyOp Minus (applyOp Times (Just 3) (Just 4)) $ applyOp Divide (Just 8) (applyOp Plus (Just 3) (Just (-3)))
|
||||
|
||||
|
||||
Now implement applyOp', which implements all methods using the do syntax.
|
||||
The Plus case is done for you once again. Be sure to check for zero with Divide.
|
||||
|
||||
> applyOp' :: Binop -> Maybe Int -> Maybe Int -> Maybe Int
|
||||
> applyOp' Plus mi mj = do
|
||||
> i <- mi
|
||||
> j <- mj
|
||||
> return $ i + j
|
||||
> applyOp' Minus mi mj = do
|
||||
> i <- mi
|
||||
> j <- mj
|
||||
> return $ i - j
|
||||
> applyOp' Times mi mj = do
|
||||
> i <- mi
|
||||
> j <- mj
|
||||
> return $ i * j
|
||||
> applyOp' Divide mi mj = do
|
||||
> i <- mi
|
||||
> j <- mj
|
||||
> if j == 0
|
||||
> then Nothing
|
||||
> else return $ i `div` j
|
||||
|
||||
More test cases:
|
||||
|
||||
> testapp1' = applyOp' Minus (applyOp' Times (Just 3) (Just 4)) $ applyOp' Divide (Just 8) (Just 2)
|
||||
> testapp2' = applyOp' Minus (applyOp' Times (Just 3) (Just 4)) $ applyOp' Divide (Just 8) (applyOp' Plus (Just 3) (Just (-3)))
|
||||
|
||||
|
||||
Finally, note the following function for incrementing and decrementing ints.
|
||||
|
||||
> mincr :: Int -> Maybe Int
|
||||
> mincr i = Just $ i + 1
|
||||
|
||||
> mdecr :: Int -> Maybe Int
|
||||
> mdecr i = Just $ i - 1
|
||||
|
||||
Experiment with these functions and the >>= syntax.
|
||||
Here is one example:
|
||||
|
||||
> testIncDec = Just 7 >>= mincr >>= mincr >>= mincr >>= mdecr
|
||||
> testIncDec' = do
|
||||
> i <- Just 7
|
||||
> i <- mincr i
|
||||
> i <- mincr i
|
||||
> i <- mincr i
|
||||
> i <- mdecr i
|
||||
> return i
|
||||
|
||||
Does bind seem more natural in this case than using do? Why or why not?
|
||||
|
||||
The bind behaves sort of like an upgraded pipe `|` from shell,
|
||||
where the output of one function is piped into the next function,
|
||||
but with the added benefit of error handling
|
||||
|
||||
The do syntax is a bit more explicit in this case, as it requires unwrapping the value,
|
||||
performing the operation, and then wrapping the value back up
|
||||
|
||||
> main :: IO ()
|
||||
> main = do
|
||||
> print testapp1
|
||||
> print testapp2
|
||||
> print testapp1'
|
||||
> print testapp2'
|
||||
> print testIncDec
|
||||
> print testIncDec'
|
||||
29
lab08/stack.hs
Normal file
29
lab08/stack.hs
Normal file
@@ -0,0 +1,29 @@
|
||||
import Control.Monad.State
|
||||
|
||||
type Stack = [Int]
|
||||
|
||||
pop :: Stack -> (Int,Stack)
|
||||
pop (x:xs) = (x,xs)
|
||||
|
||||
push :: Int -> Stack -> ((),Stack)
|
||||
push a xs = ((),a:xs)
|
||||
|
||||
stackManip :: Stack -> (Int, Stack)
|
||||
stackManip stack = let
|
||||
((),newStack1) = push 3 stack
|
||||
(a ,newStack2) = pop newStack1
|
||||
in pop newStack2
|
||||
|
||||
|
||||
pop' :: State Stack Int
|
||||
pop' = State $ \(x:xs) -> (x,xs)
|
||||
|
||||
push' :: Int -> State Stack ()
|
||||
push' a = State $ \xs -> ((),a:xs)
|
||||
|
||||
stackManip' :: State Stack Int
|
||||
stackManip' = do
|
||||
push' 3
|
||||
a <- pop'
|
||||
pop'
|
||||
|
||||
11
lab09/complex.json
Normal file
11
lab09/complex.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
name: "Complex number list example",
|
||||
nums: [
|
||||
{ real: 42, imaginary: 1 },
|
||||
{ real: 30, imaginary: 0 },
|
||||
{ real: 15, imaginary: 7 }
|
||||
],
|
||||
knownIssues: null,
|
||||
verified: false
|
||||
}
|
||||
|
||||
46
lab09/csvParser.hs
Normal file
46
lab09/csvParser.hs
Normal file
@@ -0,0 +1,46 @@
|
||||
import Text.ParserCombinators.Parsec
|
||||
import System.Environment
|
||||
|
||||
-- Each parser takes in a sequence of Chars and returns either
|
||||
-- * an array of Strings ([String])
|
||||
-- * an array of arrays of Strings ([String])
|
||||
|
||||
csvFile :: GenParser Char st [[String]]
|
||||
-- A csv file contains 0 or more lines and is terminated by the end of the file
|
||||
csvFile = do
|
||||
result <- many line -- The result is an array of an array of Strings, i.e. [[String]]
|
||||
eof -- match end of file
|
||||
return result
|
||||
|
||||
line :: GenParser Char st [String]
|
||||
-- A line is a comma separated list of values, ending with a newline character
|
||||
line = do
|
||||
result <- cells
|
||||
char '\n' -- end of line, only works in Linux or OSX formatted files
|
||||
return result
|
||||
|
||||
cells :: GenParser Char st [String]
|
||||
cells = do
|
||||
first <- cellContent
|
||||
next <- remainingCells
|
||||
return (first : next)
|
||||
|
||||
remainingCells :: GenParser Char st [String]
|
||||
remainingCells = do
|
||||
(char ',' >> cells) -- found a comma, so more cells
|
||||
<|> (return []) -- No comma, so we wrap up an empty array
|
||||
|
||||
cellContent :: GenParser Char st String
|
||||
cellContent =
|
||||
many (noneOf ",\n") -- Matches any character that is not a comma or newline
|
||||
|
||||
parseCSV :: String -> Either ParseError [[String]]
|
||||
parseCSV input = parse csvFile "(unknown)" input
|
||||
|
||||
main = do
|
||||
args <- getArgs
|
||||
p <- parseFromFile csvFile (head args)
|
||||
case p of
|
||||
Left err -> print err
|
||||
Right csv -> print csv
|
||||
|
||||
40
lab09/csvParserImproved.hs
Normal file
40
lab09/csvParserImproved.hs
Normal file
@@ -0,0 +1,40 @@
|
||||
import Text.ParserCombinators.Parsec
|
||||
import System.Environment
|
||||
|
||||
-- Each parser takes in a sequence of Chars and returns either
|
||||
-- * an array of Strings ([String])
|
||||
-- * an array of arrays of Strings ([String])
|
||||
|
||||
csvFile :: GenParser Char st [[String]]
|
||||
csvFile = line `endBy` eol -- a series of lines ended by the end of the file
|
||||
|
||||
line :: GenParser Char st [String]
|
||||
line = cell `sepBy` (char ',') -- a series of cells separated by commas
|
||||
|
||||
cell :: GenParser Char st String
|
||||
cell = many (noneOf ",\n")
|
||||
|
||||
eol :: GenParser Char st String
|
||||
{-
|
||||
eol = char '\n' -- does not work on Windows
|
||||
eol = string "\n" <|> string "\n\r" -- always matches first pattern
|
||||
eol = string "\n\r" <|> string "\n" -- fails on non-windows, since the left hand tries and consumes the \n
|
||||
eol = do -- works, but ugly, and changes the types
|
||||
char '\n'
|
||||
char '\r' <|> return '\n'
|
||||
-}
|
||||
eol = try (string "\n\r")
|
||||
<|> string "\n"
|
||||
<?> "end of line"
|
||||
|
||||
|
||||
parseCSV :: String -> Either ParseError [[String]]
|
||||
parseCSV input = parse csvFile "(unknown)" input
|
||||
|
||||
main = do
|
||||
args <- getArgs
|
||||
p <- parseFromFile csvFile (head args)
|
||||
case p of
|
||||
Left err -> print err
|
||||
Right csv -> print csv
|
||||
|
||||
116
lab09/jsonParser.hs
Normal file
116
lab09/jsonParser.hs
Normal file
@@ -0,0 +1,116 @@
|
||||
import Text.ParserCombinators.Parsec
|
||||
import System.Environment
|
||||
import Data.List (intercalate)
|
||||
|
||||
data JValue = JString String
|
||||
| JNumber Double
|
||||
| JBool Bool
|
||||
| JNull
|
||||
| JObject [(String, JValue)]
|
||||
| JArray [JValue]
|
||||
deriving (Eq, Ord, Show)
|
||||
|
||||
|
||||
jsonFile :: GenParser Char st JValue
|
||||
jsonFile = do
|
||||
result <- jsonArr <|> jsonObj
|
||||
spaces
|
||||
eof
|
||||
return result
|
||||
|
||||
jsonElem :: GenParser Char st JValue
|
||||
jsonElem = do
|
||||
spaces
|
||||
result <- jsonElem'
|
||||
spaces
|
||||
return result
|
||||
|
||||
jsonElem' = jsonArr
|
||||
<|> jsonString
|
||||
<|> jsonBool
|
||||
<|> jsonNull
|
||||
<|> jsonInt
|
||||
<|> jsonObj
|
||||
<?> "json element"
|
||||
|
||||
jsonString :: GenParser Char st JValue
|
||||
jsonString = jsonStringDQ <|> jsonStringSQ
|
||||
|
||||
jsonStringDQ = do
|
||||
char '"'
|
||||
s <- many $ noneOf "\"" -- crude. does not allow double quotes within strings
|
||||
char '"'
|
||||
return $ JString s
|
||||
|
||||
jsonStringSQ = do
|
||||
char '\''
|
||||
s <- many $ noneOf "'" -- crude, same as above
|
||||
char '\''
|
||||
return $ JString s
|
||||
|
||||
jsonBool = do
|
||||
bStr <- string "true" <|> string "false"
|
||||
return $ case bStr of
|
||||
"true" -> JBool True
|
||||
"false" -> JBool False
|
||||
|
||||
jsonNull = do
|
||||
string "null"
|
||||
return JNull
|
||||
|
||||
jsonArr = do
|
||||
char '['
|
||||
arr <- jsonElem `sepBy` (char ',')
|
||||
char ']'
|
||||
return $ JArray arr
|
||||
|
||||
jsonInt = do
|
||||
i <- many1 digit
|
||||
return $ JNumber (read i :: Double)
|
||||
|
||||
jsonObj = do
|
||||
char '{'
|
||||
members <- jsonMember `sepBy` (char ',')
|
||||
char '}'
|
||||
return $ JObject members
|
||||
|
||||
jsonMember = do
|
||||
spaces
|
||||
key <- many $ noneOf ":"
|
||||
char ':'
|
||||
value <- jsonElem
|
||||
return (key, value)
|
||||
|
||||
parseJSON :: String -> Either ParseError JValue
|
||||
parseJSON input = parse jsonFile "(unknown)" input
|
||||
|
||||
prettyPrint :: JValue -> String
|
||||
prettyPrint json = prettyPrint' json 0
|
||||
|
||||
prettyPrint' :: JValue -> Int -> String
|
||||
prettyPrint' json indent = replicate indent ' ' ++ case json of
|
||||
JString s -> show s
|
||||
JNumber n -> show n
|
||||
JBool b -> show b
|
||||
JNull -> "null"
|
||||
JArray arr -> "[\n"
|
||||
++ intercalate ",\n" (map (\x -> prettyPrint' x (indent + 2)) arr)
|
||||
++ "\n]"
|
||||
JObject members -> "{\n"
|
||||
++ intercalate ",\n" (map (
|
||||
\(k, v) -> replicate (indent + 2) ' '
|
||||
++ k
|
||||
++ ": "
|
||||
++ prettyPrint' v 0) members)
|
||||
++ "\n"
|
||||
++ replicate indent ' '
|
||||
++ "}"
|
||||
|
||||
main = do
|
||||
args <- getArgs
|
||||
p <- parseFromFile jsonFile (head args)
|
||||
case p of
|
||||
Left err -> print err
|
||||
Right json -> putStrLn (prettyPrint json)
|
||||
|
||||
|
||||
5
lab09/numList.json
Normal file
5
lab09/numList.json
Normal file
@@ -0,0 +1,5 @@
|
||||
[
|
||||
42,
|
||||
33,
|
||||
71
|
||||
]
|
||||
1
lab09/strList.json
Normal file
1
lab09/strList.json
Normal file
@@ -0,0 +1 @@
|
||||
['hi',"how",'are','u',null]
|
||||
4
lab09/test.csv
Normal file
4
lab09/test.csv
Normal file
@@ -0,0 +1,4 @@
|
||||
Year,Make,Model,Length
|
||||
1997,Ford,E350,2.34
|
||||
2000,Mercury,Cougar,2.38
|
||||
|
||||
|
317
lab10/lambdaCalc.lhs
Normal file
317
lab10/lambdaCalc.lhs
Normal file
@@ -0,0 +1,317 @@
|
||||
PART 1 -- Introduction to Lambda Calculus
|
||||
|
||||
Lambda-calculus is the world's smallest programming language.
|
||||
In its entirety, it consists of:
|
||||
*functions (sometimes called abstractions)
|
||||
*function application
|
||||
*variables
|
||||
|
||||
Since the Greek letter lambda is not available in ASCII, a number of
|
||||
programming languages use the backslash character instead. For instance,
|
||||
in Haskell the identity function (lambda x.x) is written as follows:
|
||||
|
||||
> id = \x -> x
|
||||
|
||||
Despite the minimal nature of lambda-calculus, it is Turing-complete,
|
||||
and we can encode a wide array of language features.
|
||||
|
||||
|
||||
PART 2 -- Boolean values
|
||||
|
||||
To begin with, lets define boolean values.
|
||||
We will name them 'tru' and 'fls' to avoid confusion with Haskell's
|
||||
own constructs.
|
||||
|
||||
> tru = \x -> \y -> x
|
||||
> fls = \x -> \y -> y
|
||||
|
||||
Defining booleans as functions may seem odd. In this case,
|
||||
'tru' is really 'returnFirstParameter' and 'fls' is
|
||||
'returnSecondParameter'. We can use these values to form an
|
||||
'if then else' construct, which we will call 'test':
|
||||
|
||||
> test = \cond -> \thn -> \els -> cond thn els
|
||||
|
||||
The above function takes the boolean value and then applies it to the two
|
||||
expressions. In the case that it is 'tru', it will result in
|
||||
returning the first expression. If it is 'false', it will return
|
||||
the second expression.
|
||||
|
||||
> testTru = transBool $ test tru tru fls
|
||||
> testFls = transBool $ test fls tru fls
|
||||
|
||||
Note that Haskell cannot show lambdas, so the above test cases use a helper function;
|
||||
everything to the right of the $ is lambda calculus.
|
||||
Note that the transBool function can make use of the lambda calculus test function.
|
||||
|
||||
> transBool b = test b "true" "false"
|
||||
|
||||
With our boolean values, we can also define logical operators, such as 'and'.
|
||||
(We will call it 'andd' to avoid conflict's with Haskell's 'and'.
|
||||
|
||||
> andd = \b -> \c -> b c fls
|
||||
|
||||
Why does this work? Here are a couple of tests to consider.
|
||||
|
||||
> testAnd1 = transBool $ andd tru tru
|
||||
> testAnd2 = transBool $ andd fls tru
|
||||
|
||||
|
||||
Define the 'nott' and 'orr' operators below without using andd.
|
||||
|
||||
> nott = \b -> b fls tru
|
||||
|
||||
> orr = \b -> \c -> b tru c
|
||||
|
||||
Here are some test cases:
|
||||
|
||||
> testOps1 = transBool $ orr fls (nott fls)
|
||||
> testOps2 = transBool $ nott (orr fls tru)
|
||||
|
||||
|
||||
|
||||
PART 3 -- Pairs
|
||||
|
||||
We can also encode data structures in lambda calculus.
|
||||
We'll look at pairs.
|
||||
|
||||
> pair = \f -> \s -> \b -> b f s
|
||||
|
||||
When this function is applied to two arguments,
|
||||
'f' is bound to the first item in our pair and 's' to the second item.
|
||||
The following is equivalent to (\b -> b tru fls):
|
||||
|
||||
> myPair = pair tru fls
|
||||
|
||||
In order for this to be useful, we must be able to get items out of the pair.
|
||||
The following functions will get the first and second items, respectively:
|
||||
|
||||
> first = \p -> p tru
|
||||
> second = \p -> p fls
|
||||
|
||||
The following lines produce "true" and "false" respectively.
|
||||
Again, everything to the right of the $ is lambda calculus.
|
||||
|
||||
> testPairFst = transBool $ first myPair
|
||||
> testPairSnd = transBool $ second myPair
|
||||
|
||||
From pairs, we can begin to build other data structures such as lists.
|
||||
|
||||
|
||||
|
||||
PART 4 -- Lists
|
||||
|
||||
Following the Lisp approach, we can define lists as pairs, where the first
|
||||
part of the pair is the 'head' of the list, and the second element of the
|
||||
list is the 'tail' -- the list containing all of the elements except for
|
||||
the head.
|
||||
|
||||
In Lisp, the function that creates a list is called 'cons' -- we will use
|
||||
that name here. Instead of 'head' and 'tail', we will use 'hd' and 'tl'
|
||||
to avoid conflicts with the Haskell functions of the same name. As you can
|
||||
see, we can easily define three of the basic list operations with minimal
|
||||
effort:
|
||||
|
||||
> cons = pair
|
||||
> hd = first
|
||||
> tl = second
|
||||
|
||||
However, we need two more pieces; we need to have a way of representing
|
||||
the empty list 'nil', and we need a way to test if a list is empty.
|
||||
|
||||
For 'isEmpty', we can exploit the design of pairs. As a reminder, pairs
|
||||
are of the form (\b -> b f s). The 'b' is supposed to be a boolean,
|
||||
which selects whether to return the first or second element. However,
|
||||
we will instead provide it a lambda that ignores its arguments and
|
||||
always returns false:
|
||||
|
||||
> isEmpty = \lst -> lst (\h -> \t -> fls)
|
||||
|
||||
With this definition of 'isEmpty', we now know what we need for 'nil'.
|
||||
The empty list should always return true when it is passed in to 'isEmpty'.
|
||||
|
||||
Write the definition for 'nil' below. (If you are unsure of what the
|
||||
definition should be, try walking through the derivation of
|
||||
"isEmpty (pair 1 2)".
|
||||
|
||||
> nil = \x -> tru
|
||||
|
||||
Now we can create some sample lists. (Note that we are using Haskell numbers
|
||||
just for easy of testing, though they are not part of the lambda calculus.)
|
||||
|
||||
> list1 = cons 0 (cons 1 (cons 2 nil))
|
||||
> list2 = cons 3 (cons 4 (cons 5 nil))
|
||||
|
||||
The following test cases should print "0", "1", "false", and "true"
|
||||
|
||||
> testListHead = hd list1
|
||||
> testListHeadTail = hd (tl list1)
|
||||
> testNotEmpty = transBool $ isEmpty list1
|
||||
> testEmpty = transBool $ isEmpty (tl (tl (tl list1)))
|
||||
|
||||
|
||||
|
||||
PART 5 -- Church numerals
|
||||
|
||||
With Boolean values, we were able to represent "primitive" values by
|
||||
using special functions. However, there are only two possible values.
|
||||
How can we represent a potentially infinite set of values, like numbers?
|
||||
|
||||
Church numerals define numbers as a collection of functions.
|
||||
Below we will represent the numbers 0 through three:
|
||||
|
||||
> zero = \s -> \z -> z
|
||||
> one = \s -> \z -> s z
|
||||
> two = \s -> \z -> s (s z)
|
||||
> three = \s -> \z -> s (s (s z))
|
||||
|
||||
So a number is represented by the number of 's' applications in the body.
|
||||
Why is this a useful representation? And what is s exactly?
|
||||
|
||||
In these functions, z represents zero, and s represents a "successor".
|
||||
As you might guess, we will need to create this successor function.
|
||||
|
||||
> scc = \n -> \s -> \z -> s (n s z)
|
||||
|
||||
So, instead of defining one, two, three manually, we could instead derive them
|
||||
from zero.
|
||||
|
||||
> one' = scc zero
|
||||
> two' = scc one'
|
||||
> three' = scc two'
|
||||
|
||||
Walking through `scc zero` gives us the following steps:
|
||||
(\n -> \s -> \z -> s (n s z)) zero
|
||||
-> (\s -> \z -> s (zero s z))
|
||||
= (\s -> \z -> s ((\s' -> \z' -> z') s z))
|
||||
[expanding zero -- we use s' and z' to avoid confusion]
|
||||
-> (\s -> \z -> s (\z' -> z') z)
|
||||
-> (\s -> \z -> s z)
|
||||
|
||||
Therefore `scc one'` will have the following steps:
|
||||
(\n -> \s -> \z -> s (n s z)) one'
|
||||
-> (\s -> \z -> s (one' s z))
|
||||
= (\s -> \z -> s ((\s' -> \z' -> s' z') s z))
|
||||
-> (\s -> \z -> s ((\z' -> s z') z))
|
||||
-> (\s -> \z -> s (s z))
|
||||
|
||||
So is this just a cute trick, or do the Church numerals actually represent numbers
|
||||
in the real world in some meaningful way? Note that each number is essentially
|
||||
instructions for how to construct itself given a successor function and zero.
|
||||
Exploiting this fact, we can write a fairly simple function to translate
|
||||
Church numerals into Haskell Ints with the right successor function and zero value.
|
||||
|
||||
> transChurchNums n = n (+1) 0
|
||||
|
||||
Using this principle, we can define addition of two numbers (m and n)
|
||||
as getting the successor to n, m times.
|
||||
|
||||
> plus = \m -> \n -> \s -> \z -> m s (n s z)
|
||||
|
||||
> test3plus2 = transChurchNums $ plus three two
|
||||
|
||||
At a step by step level, here is what happens when the above code is invoked:
|
||||
plus three two
|
||||
= (\m -> \n -> \s -> \z -> m s (n s z)) three two
|
||||
->* \s -> \z -> three s (two s z)
|
||||
[In reality, the evaluation would stop here]
|
||||
= \s -> \z -> three s ((\s' -> z' -> s' (s' z')) s z)
|
||||
->* \s -> \z -> three s (s (s z))
|
||||
= \s -> \z -> (\s' -> z' -> s' (s' (s' z'))) s (s (s z))
|
||||
-> \s -> \z -> (\z' -> s (s (s z'))) (s (s z))
|
||||
-> \s -> \z -> s (s (s (s (s z)))) [z' replaced with (s (s z))]
|
||||
|
||||
|
||||
Alternately, we could define plus with our scc function:
|
||||
|
||||
> plus' = \m -> \n -> m scc n
|
||||
|
||||
|
||||
Define multiplication in a similar manner:
|
||||
|
||||
> multiply = \m -> \n -> m (plus n) zero
|
||||
|
||||
> test3times2 = transChurchNums $ multiply three two
|
||||
|
||||
|
||||
For ease of use, you may use the following function to convert a Haskell integer
|
||||
to a Church numeral.
|
||||
|
||||
> church n = if n == 0
|
||||
> then
|
||||
> (\s -> \z -> z)
|
||||
> else
|
||||
> (\s -> \z -> s (church (n-1) s z))
|
||||
|
||||
|
||||
|
||||
PART 6 -- Recursion
|
||||
|
||||
Consider the following function, known as the divergent combinator.
|
||||
|
||||
omega = (\x -> x x) (\x -> x x)
|
||||
|
||||
Haskell will not accept the above combinator.
|
||||
Evaluate this function by hand yourself.
|
||||
After one step, what do you get?
|
||||
|
||||
function: (\x -> x x)
|
||||
argument: (\x -> x x)
|
||||
substitute x with (\x -> x x) in x x = (\x -> x x) (\x -> x x)
|
||||
it evaluates to the same function, basically an infinite loop I think?
|
||||
|
||||
|
||||
The omega function is not terribly useful, though it is interesting.
|
||||
We can cause a lambda calculus program to go into an infinite loop.
|
||||
|
||||
A related function is the **fix combinator**, sometimes called the Y combinator.
|
||||
(Like omega, fix cannot be run in Haskell).
|
||||
|
||||
fix = \f -> (\x -> f (\y -> x x y)) (\x -> f (\y -> x x y))
|
||||
|
||||
With the fix combinator, we can create recursive functions.
|
||||
Let's consider the definition for factorial.
|
||||
First, we need a way to test for zero.
|
||||
|
||||
> isZero = \n -> n (\x -> fls) tru
|
||||
|
||||
We also need a predecessor function, which proves to be fairly complex.
|
||||
|
||||
zz = pair zero zero
|
||||
ss = \p -> pair (snd p) (plus one (snd p))
|
||||
prd = \m -> fst (m ss zz)
|
||||
|
||||
With the fix combinator, we need to define a function that effectively does
|
||||
one step of the recursive evaluation. Note the 'fct' parameter in the function
|
||||
below.
|
||||
|
||||
g = \fct -> \n -> (test (isZero (prd n)) one (multiply n (fct (prd n))))
|
||||
|
||||
Using g and the fix function, we can define factorial.
|
||||
|
||||
factorial = fix g
|
||||
|
||||
To understand how this works, write out the evaluation steps for `factorial 3`.
|
||||
|
||||
-> factorial 3 = (fix g) 3
|
||||
-> g (fix g) 3 = test (isZero (prd 3)) one (multiply 3 (g (prd 3)))
|
||||
-> test (isZero (2)) one (multiply 3 (g (2)))
|
||||
takes the false branch until g (1) then takes the true branch because isZero (prd 1) = true
|
||||
|
||||
> main :: IO ()
|
||||
> main = do
|
||||
> print $ testTru ++ " : expected true"
|
||||
> print $ testFls ++ " : expected false"
|
||||
> print $ testAnd1 ++ " : expected true"
|
||||
> print $ testAnd2 ++ " : expected false"
|
||||
> print $ testOps1 ++ " : expected true"
|
||||
> print $ testOps2 ++ " : expected false"
|
||||
> print $ testPairFst ++ " : expected true"
|
||||
> print $ testPairSnd ++ " : expected false"
|
||||
> print $ show testListHead ++ " : expected 0"
|
||||
> print $ show testListHeadTail ++ " : expected 1"
|
||||
> print $ testNotEmpty ++ " : expected false"
|
||||
> print $ testEmpty ++ " : expected true"
|
||||
> print $ show test3plus2 ++ " : expected 5"
|
||||
> print $ show test3times2 ++ " : expected 6"
|
||||
|
||||
23
lab11/functional.js
Normal file
23
lab11/functional.js
Normal file
@@ -0,0 +1,23 @@
|
||||
var foldl = function (f, acc, array) {
|
||||
}
|
||||
|
||||
console.log(foldl(function(x,y){return x+y}, 0, [1,2,3]));
|
||||
|
||||
var foldr = function (f, z, array) {
|
||||
}
|
||||
|
||||
console.log(foldl(function(x,y){return x/y}, 1, [2,4,8]));
|
||||
|
||||
var map = function (f, array) {
|
||||
}
|
||||
|
||||
console.log(map(function(x){return x+x}, [1,2,3,5,7,9,11,13]));
|
||||
|
||||
|
||||
// Write a curry function as we discussed in class.
|
||||
// Create a `double` method using the curry function
|
||||
// and the following `mult` function.
|
||||
function mult(x,y) {
|
||||
return x * y;
|
||||
}
|
||||
|
||||
125
lab11/intro.js
Normal file
125
lab11/intro.js
Normal file
@@ -0,0 +1,125 @@
|
||||
// Highlights some of the basic functionality of JavaScript.
|
||||
|
||||
// With JavaScript, we can specify new values with the var keyword.
|
||||
// Using the keyword var is not strictly required, but it is considered a best practice.
|
||||
var x=42, y=7;
|
||||
|
||||
|
||||
// JavaScript is a functional language, in the sense that functions are first class objects.
|
||||
// (WARNING: not everyone likes this definition of functional languages).
|
||||
// We can write them in two different ways.
|
||||
function add(a,b) {
|
||||
return a + b;
|
||||
}
|
||||
|
||||
var square = function(a) {
|
||||
return a * a;
|
||||
}
|
||||
|
||||
// Printing varies by platform. Node uses console.log.
|
||||
console.log("x + y = " + add(x,y));
|
||||
|
||||
// That is annoying, so let's store it in another variable.
|
||||
var print = console.log;
|
||||
|
||||
print("x^2 = " + square(x));
|
||||
|
||||
// Since functions are first class values, we can do interesting stuff with them.
|
||||
function applyFunToX(f) {
|
||||
f(x);
|
||||
}
|
||||
|
||||
applyFunToX(print);
|
||||
|
||||
// JavaScript functions are **closures**. They remember their surrounding scope.
|
||||
var getNextInt = function() {
|
||||
var nextInt = 0;
|
||||
return function() {
|
||||
return nextInt++;
|
||||
}
|
||||
}();
|
||||
|
||||
print(getNextInt());
|
||||
print(getNextInt());
|
||||
print(getNextInt());
|
||||
|
||||
|
||||
// Objects in JavaScript work a little differently.
|
||||
var complex = { real: 3, imaginary: 1 };
|
||||
print (complex);
|
||||
|
||||
var Dog = {
|
||||
speak: function() { print('bark!'); }
|
||||
};
|
||||
|
||||
rex = { name: 'rex', __proto__: Dog } // Manually setting the prototype chain -- not universal
|
||||
rex.speak();
|
||||
|
||||
// We can add or remove properties at runtime
|
||||
rex['favoriteToy'] = 'squeaky ball'; // could write this as rex.favoriteToy
|
||||
print(rex);
|
||||
//delete rex.name;
|
||||
|
||||
// We can override properties if we wish
|
||||
rex.speak = function() { print('grr....'); }
|
||||
rex.speak();
|
||||
|
||||
// Or delete them, with perhaps surprising results
|
||||
delete rex.speak;
|
||||
rex.speak();
|
||||
|
||||
|
||||
// The more standard way of adding JavaScript objects: first create a constructor
|
||||
function Cat(name, breed) {
|
||||
this.name = name;
|
||||
this.breed = breed;
|
||||
this.speak = function() { print('meow!'); }
|
||||
}
|
||||
|
||||
var garfield = new Cat('Garfield', 'Orange tabby');
|
||||
print(garfield);
|
||||
garfield.speak();
|
||||
|
||||
Cat.prototype.makeAngryNoise = function() {
|
||||
print('hiss');
|
||||
}
|
||||
garfield.makeAngryNoise();
|
||||
|
||||
Cat.prototype.sayName = function() {
|
||||
print("My name is " + this.name);
|
||||
}
|
||||
garfield.sayName();
|
||||
|
||||
garfield.favoriteFood = 'lasagna';
|
||||
|
||||
var animals = [garfield, new Cat('mimi', 'Calico'), rex];
|
||||
|
||||
var forEach = function(arr,f) {
|
||||
var i;
|
||||
for (i=0; i<arr.length; i++) {
|
||||
f(arr[i]);
|
||||
}
|
||||
}
|
||||
print("Displaying all animals");
|
||||
forEach(animals, print);
|
||||
|
||||
print("Each animal say your name");
|
||||
forEach(animals, function(animal) {
|
||||
animal.speak();
|
||||
//animal.sayName(); // Won't work -- rex has no sayName method
|
||||
Cat.prototype.sayName.apply(animal);
|
||||
});
|
||||
|
||||
|
||||
Function.prototype.curry = function() {
|
||||
var slice = Array.prototype.slice,
|
||||
args = slice.apply(arguments),
|
||||
that = this;
|
||||
return function () {
|
||||
return that.apply(null, args.concat(slice.apply(arguments)));
|
||||
};
|
||||
};
|
||||
|
||||
var addOne = add.curry(1);
|
||||
console.log(addOne(3));
|
||||
|
||||
59
lab11/student.js
Normal file
59
lab11/student.js
Normal file
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
Create a 'Student' constructor, like we did for Cat in class.
|
||||
It should have the following fields:
|
||||
*firstName
|
||||
*lastName
|
||||
*studentID
|
||||
*display -- A function that prints out the firstName, lastName, and studentID number.
|
||||
To invoke it, you should call `student.display()`.
|
||||
|
||||
Create an array of new students.
|
||||
Add a 'graduated' property to just one of your students.
|
||||
|
||||
Now create another student **without** using the constructor.
|
||||
(In other words, use the object literal `{}` syntax).
|
||||
Set the prototype chain manually using the __proto__ field.
|
||||
Make sure the display method still works (without you having to add it to the object explicitly).
|
||||
*/
|
||||
|
||||
// Create a Student constructor
|
||||
function Student(firstName, lastName, studentID) {
|
||||
this.firstName = firstName;
|
||||
this.lastName = lastName;
|
||||
this.studentID = studentID;
|
||||
}
|
||||
|
||||
// Add the display method to the prototype
|
||||
// Manual student doesn't work if display is defined in the constructor
|
||||
Student.prototype.display = function () {
|
||||
console.log(`${this.firstName} ${this.lastName}, ID: ${this.studentID}`);
|
||||
};
|
||||
|
||||
var students = [
|
||||
new Student("John", "Smith", "12345"),
|
||||
new Student("Jane", "Doe", "67890"),
|
||||
new Student("Alice", "Johnson", "11111"),
|
||||
];
|
||||
|
||||
// Add a 'graduated' property to just one of the students
|
||||
students[0].graduated = true;
|
||||
|
||||
// Student without using the constructor (object literal syntax)
|
||||
var manualStudent = {
|
||||
firstName: "Bob",
|
||||
lastName: "Wilson",
|
||||
studentID: "99999",
|
||||
__proto__: Student.prototype,
|
||||
};
|
||||
|
||||
// Test
|
||||
console.log("Students created with constructor:");
|
||||
students.forEach(function (student) {
|
||||
student.display();
|
||||
if (student.graduated) {
|
||||
console.log("This student has graduated!");
|
||||
}
|
||||
});
|
||||
|
||||
console.log("\nManual student with prototype:");
|
||||
manualStudent.display();
|
||||
79
lab12/chatServer.js
Normal file
79
lab12/chatServer.js
Normal file
@@ -0,0 +1,79 @@
|
||||
const net = require('net');
|
||||
const eol = require('os').EOL;
|
||||
const crypto = require('crypto');
|
||||
|
||||
const HASH_ALG = 'sha256';
|
||||
const NAME_LEN = 10;
|
||||
|
||||
var srvr = net.createServer();
|
||||
var clientList = [];
|
||||
|
||||
srvr.on('connection', function (client) {
|
||||
client.name = calcName(client.remoteAddress, client.remotePort);
|
||||
client.write('Welcome, ' + client.name + eol);
|
||||
clientList.push(client);
|
||||
|
||||
client.on("data", function (data) {
|
||||
if (data.at(0) !== "\\".charCodeAt(0)) {
|
||||
broadcast(data, client);
|
||||
return;
|
||||
}
|
||||
command = data
|
||||
.subarray(1)
|
||||
.toString()
|
||||
.split(/[\s\r\n]+/)
|
||||
.filter((s) => s.length > 0);
|
||||
console.log(command);
|
||||
switch (command[0]) {
|
||||
case "list":
|
||||
const list = clientList.map((c) => c.name).join(eol);
|
||||
client.write("Connected Users" + eol + list + eol);
|
||||
break;
|
||||
case "rename":
|
||||
if (command.length !== 2) {
|
||||
client.write("Usage: \\rename <newname>" + eol);
|
||||
break;
|
||||
}
|
||||
const oldName = client.name;
|
||||
client.name = command[1];
|
||||
client.write("You are now known as " + client.name + eol);
|
||||
broadcast(oldName + " is now known as " + client.name + eol, client);
|
||||
break;
|
||||
case "private":
|
||||
if (command.length < 3) {
|
||||
client.write("Usage: \\private <name> <msg>" + eol);
|
||||
break;
|
||||
}
|
||||
const targetName = command[1];
|
||||
const msg = command.slice(2).join(" ");
|
||||
const targetClient = clientList.find((c) => c.name === targetName);
|
||||
if (targetClient) {
|
||||
targetClient.write("(Private) " + client.name + " says " + msg + eol);
|
||||
client.write("(Private) To " + targetClient.name + ": " + msg + eol);
|
||||
} else {
|
||||
client.write("User " + targetName + " not found." + eol);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
client.write("Unknown command: " + command[0] + eol);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function calcName(remoteAddress, remotePort) {
|
||||
let data = remoteAddress + ":" + remotePort;
|
||||
let h = crypto.createHash(HASH_ALG).update(data).digest('hex')
|
||||
return h.substring(0, NAME_LEN);
|
||||
}
|
||||
|
||||
function broadcast(data, client) {
|
||||
for (var i in clientList) {
|
||||
if (client !== clientList[i]) {
|
||||
clientList[i].write(client.name + " says " + data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
srvr.listen(9000);
|
||||
|
||||
|
||||
25
lab12/keys/alicePriv.txt
Normal file
25
lab12/keys/alicePriv.txt
Normal file
@@ -0,0 +1,25 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEowIBAAKCAQEAifK0oKcMmyyCBSSramC4RtcxGNkXUPcs3towqvEAhPisZeXaskG5JnvK
|
||||
Tk1tdIsDcG9ioUOO3SKo1UwSC1tWf0XZiSxRWDh4fM7m+SoMBHcyE2rAo/EtOtv+hsa9Jsjg
|
||||
q28eD9BJZyZJkFrz+pY6hrIQNeLJp4Iq1BlLwzni6Mhea7rBoU0pUG3rrvXMPoG/lPcOC5yi
|
||||
bjL2fKAojG66ML5nfFDp3Cda/ccs8K6e/89cSwgYQBgz1qntd7djSW8moURrPsDdwhFo6/Dm
|
||||
Ctds4Ct/sCilkGyz6NydK3n1wKk+JiRQlvmCZcOpiy9ywsq+t336Ly8JIIQ8fBdOKvSjAwID
|
||||
AQABAoIBAB3Fg7YoV1AmCumRQG108lBXoQD72UxgDA8Mh/RcjwAjqxFSyiAujwb/MGo1y6mb
|
||||
VuKtJ1wmeG7BTC68fCh3TfOp34j7R5MCnP5SqQadTHqqPuRx5aF8K4bIc+YifjAgqlxVXueK
|
||||
zEy2tqmw93bddUQcqeL3Tw4XyaZ8zx0TISWbx6S8cI8VKYOz5l+8itNM8lH79i0IxKfAMeUR
|
||||
QF0jfRxzWLCuOedY1/19/Q9rY9BB6pgi+7bh6hgMUbPRzWegMpuMuGHjk+23Ah6QKfSO4bd4
|
||||
9Wq8CqYVLCqRpTUiSfHChXopnm3jGp6ELGYOOqxrI38M3X4izW2kv0mnk/o7UAECgYEAvw8L
|
||||
laZs1j8LrisSiEp+453qlGB+Pq3h4OJK6gnKZDiD0K8K4sXl5a6wNTOlx77gdL6Xmmevqp8J
|
||||
I787+TG/K4nrBnRObPPo/Jr6Ug/ON3oCRzhm5Ul5WMcJmkxEIgdJOzTxmuPdlMco9hU1rN3R
|
||||
OzMgce9smZ0RUdEWN66naaMCgYEAuNY9RBjj7v95zfTGsbYv7xj2UYq0FBqU9s+PpMQFKfPg
|
||||
bsPhoxOZgaWR2olVhj8XimhIv2eClOkISSORZryCVFzdFlYalrfzbj4ZLMzUrcEwhEY1x8Sj
|
||||
GRwSb6LW23hH2CR47OBCAB/I1pskClXPhjqDPeoI4w2LlmzoOch6NyECgYBC65coLKnUhTKn
|
||||
J6kPGeDGAysGG6I8fBUw+rI4tYMxhWYOMzGB6sUypPRNPjDj/OSLn7JkP8sU3S2LWtE74IPQ
|
||||
UTeEBDrwCKLjz7i8A5M8dTzVC1lnGEwrn1OquunmbVzmIw45LXUQRo3/W8B4Mx21OZw0rCMC
|
||||
zzQgzzaT/FXhNwKBgQCKl3lQuikOZ1j5aPQjBCxk5x6qfYoRGO1hMrjOHnyQvGAoLY/eKzAK
|
||||
2g7HAfqyslQAX7lMa1yqMyEXVigMFITUpNzRhrZhFITewIk34k4GCrlKNSrWI0b36OiZ9EuO
|
||||
PF4CXzacxno2hZ6d663WYgk86FrH7dfEB8AGhpqJ2RxqgQKBgD+NoVR7ER6o40pmCiBIT6O6
|
||||
A8C3gSUZcNpNFRzB6EOZHPtRvmJ9xSmw5V/nRqIL744f+UYgTggbSmW1IJLnbbUX+hKkrscV
|
||||
ldcil/J5xr56lm3eHpNiQc/h0mIWMSJwYl2YfMYmK6alASQzyfi1ibyw6PXMCYXm2MPKadI3
|
||||
FjHP
|
||||
-----END RSA PRIVATE KEY-----
|
||||
7
lab12/keys/alicePub.txt
Normal file
7
lab12/keys/alicePub.txt
Normal file
@@ -0,0 +1,7 @@
|
||||
-----BEGIN RSA PUBLIC KEY-----
|
||||
MIIBCgKCAQEAifK0oKcMmyyCBSSramC4RtcxGNkXUPcs3towqvEAhPisZeXaskG5JnvKTk1t
|
||||
dIsDcG9ioUOO3SKo1UwSC1tWf0XZiSxRWDh4fM7m+SoMBHcyE2rAo/EtOtv+hsa9Jsjgq28e
|
||||
D9BJZyZJkFrz+pY6hrIQNeLJp4Iq1BlLwzni6Mhea7rBoU0pUG3rrvXMPoG/lPcOC5yibjL2
|
||||
fKAojG66ML5nfFDp3Cda/ccs8K6e/89cSwgYQBgz1qntd7djSW8moURrPsDdwhFo6/DmCtds
|
||||
4Ct/sCilkGyz6NydK3n1wKk+JiRQlvmCZcOpiy9ywsq+t336Ly8JIIQ8fBdOKvSjAwIDAQAB
|
||||
-----END RSA PUBLIC KEY-----
|
||||
25
lab12/keys/bobPriv.txt
Normal file
25
lab12/keys/bobPriv.txt
Normal file
@@ -0,0 +1,25 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpQIBAAKCAQEA4g5WrWWU+FRbrBbT0J8vADRic+kYkggBFARM3VxWIKOA3o6/6oUi4PIE
|
||||
dAocsLmYegL8c/xp/OTP/qpOZdFWOZ4/QgO/9ru9hbCIEUDQ1E3FwDCP835OHAZ7yv75gRD2
|
||||
zcxsX95FZnN9Z3InwW3tNr59k97921OIV+Zjc55+HDHjovN+dM29jP72o1zBIRYsJxBSt1Op
|
||||
hwkuDGA/IFwDrHqbidrFDR8cgGw6S0OV8TqhOaLmvCwXskJfp3/9uxo0JOO/rxKBU0WaqgU/
|
||||
p816qtwVqdHmYkI04WxbihVvMdFFEMdv9l6igJlC6vbVO+qaCVBa17939nyuhexmJQP8IwID
|
||||
AQABAoIBAELje0JTjIDudTG2j3Zro7yrw3YIwvxy4d7KZvQZHmMJ/lfcZW0/d9KXyjmLxHGG
|
||||
eVehCpJHXhU2n1T7j7cQ+jKSYKVn9qVLaAjm81wnkaNAYAM+W1nxRauFP5wRVkKagmzi0C6N
|
||||
1ns6MSEq5hiWLlrEguP+V59OWEmqIkNT4nz0t8iuF7HPaIHIx9X/+tDCRjJT9+y3L7qKkXjm
|
||||
CdZf9x8MZCj9PffwjGwLhGUW5PTm140p4g3+KgVkQxDyaJnXmsMJxSrs9x4QTfJtrdtNt+Yv
|
||||
jKF+e0kwEiKSKbGBGl/KeaFSA3O8lxMnSswim3/3R09Hwd5DTIaJPafYXjmBsgECgYEA8jRh
|
||||
qmhzX09OcKkQOWcUUc6CtV/FL57hlP4t5cWR83ABAWmiagw4JStsnfw75VKo9zl9mlicXyYB
|
||||
w4AVJo0eDKaxYn7YF8N441vQtpuvrGayulQg9CdggRfOWulIQH6g48JcpS06TeLAcTMDd6Py
|
||||
4I9CzpAb3H/EJty7DNhdl9cCgYEA7u599/UGYIx1QSLADq/oNA3boY6ybkRSh0RhoF9OiDkM
|
||||
kdE0di7Bu9AJHBboS+wkgnMH8vA7Vn6LsWslpStylqp+S5hU/845IAj7gHrazm0KRSSKi+XS
|
||||
Ijipqz8TJqloptqDpzmC0JtVd3tdTD1RL6yMDePf38UB+sMX7BLGxJUCgYEAmNISOZpUCXQZ
|
||||
sx4JPZ7bhE5/zH8UBNsDzKWgQhUp5Obry39iWfVBy7ode4V2KVjBgdQFe0ijyalKfZcoQT3T
|
||||
EvpufA46vKu6cAIzq7SJ4IagCM8iR8s8qddifZwp2X/MdkjhedsSqagD8qjUbD3G9oeOCJWG
|
||||
7tay5i5p2039p9sCgYEAkvEnlYQijOQ1BH8LoLy8UK8pIN2yjLAGFVyPseKLWKnFOjRwUDKH
|
||||
QSs7PwQzjuXGuldmTxlBVPt0jAN6Xm8vBMwL6g/gToYUicgcbLKhuIxE4asMbedEaXlxl3iu
|
||||
HXvYea0vhHuRIRw23smG4mlfcbNrWuuG9oahpgevwab2iVUCgYEAj0F7DiCUg2sJsLf1aZfi
|
||||
5bjPCqA5oszwyuzB9j6SYDJ15Jprc744u7MdOTcnTVM/5k2hOgjTSWJYm9ghm7V9NAWFpyaP
|
||||
2k4IR6686XF3us4Oh+LYNd1YqjgB+XW/cjS6bWtyk/nFJYAw9Ois2mTyuc0wba0xrP/n3oxT
|
||||
QyzNeNs=
|
||||
-----END RSA PRIVATE KEY-----
|
||||
7
lab12/keys/bobPub.txt
Normal file
7
lab12/keys/bobPub.txt
Normal file
@@ -0,0 +1,7 @@
|
||||
-----BEGIN RSA PUBLIC KEY-----
|
||||
MIIBCgKCAQEA4g5WrWWU+FRbrBbT0J8vADRic+kYkggBFARM3VxWIKOA3o6/6oUi4PIEdAoc
|
||||
sLmYegL8c/xp/OTP/qpOZdFWOZ4/QgO/9ru9hbCIEUDQ1E3FwDCP835OHAZ7yv75gRD2zcxs
|
||||
X95FZnN9Z3InwW3tNr59k97921OIV+Zjc55+HDHjovN+dM29jP72o1zBIRYsJxBSt1Ophwku
|
||||
DGA/IFwDrHqbidrFDR8cgGw6S0OV8TqhOaLmvCwXskJfp3/9uxo0JOO/rxKBU0WaqgU/p816
|
||||
qtwVqdHmYkI04WxbihVvMdFFEMdv9l6igJlC6vbVO+qaCVBa17939nyuhexmJQP8IwIDAQAB
|
||||
-----END RSA PUBLIC KEY-----
|
||||
25
lab12/keys/charliePriv.txt
Normal file
25
lab12/keys/charliePriv.txt
Normal file
@@ -0,0 +1,25 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEogIBAAKCAQEAh3tLOaiZvejIsA08OBMqYB4zdn5emWjcJpIHObW8H5jrcj9hpGe1IZ+Q
|
||||
BpZ53318aD0uoJPSnOLXcLQR5v8AVdddmwaJr3meaW8PL/J5BPMpDyknxk2GgvZRxXy/eQkv
|
||||
VLXbEIRzG5+8Zj8PXkc6rV+qlJEkK/0kuN2y9wz/vYN5z5Fq5qmGnZO665HBh15OxM9b30Fl
|
||||
iBA/DXmD+zK8oR3Gwk3W8GISWmckAqFegZQr9ocY73JHFGTXYmxVbGiWvpmEIuES57bKQX4f
|
||||
g3Xa2yQnW8jco48YX6ScPSpVWRiP+56YgAsoojHYr/Evn+fVE4OLqlLrNgWp9UWE6OKqVwID
|
||||
AQABAoIBAHdfGxjihRPfV3RHNRfzSxMFiTKOhauFyPPzFoqypd7xTgKL9ex1tBK8FJq5Wwif
|
||||
+E2gGsVBegDhnhGfl2aAqDr6V2+QgyApeHpNcIQ+kILb/QFusaz4AdzCS6pGEC8E+L8avLoA
|
||||
+asNQ2KTjWb0O68m65WbjNN1aDW6oUUsHulK8wKC9M2pDs+nTd18IAkFVOop31rJ09bZcsnr
|
||||
qiofRxjLhilvmH0F4nmUTfjrvNxNR0dWpeMEOnoIOg3OtTDqGcJFUI7ELwhYFt3vRP1sRXMS
|
||||
mqgBmLmHK1p8B02YlVjXfmxAMR1ONk+G4uhLLdKkA+9DTNuc8PfEm7Ox+TXV9gECgYEA159D
|
||||
kR/FnZhf64gylHKJei554oGB/EapC+LIViwHptSJ38Vk3vkWKomKPKmi3Se+S4e0FkEbgSqv
|
||||
Tje6IzZwYFxEoZE+UNJSLXYTtnus4yhCItc0FHbCz+4bKuLf7Kij0b1L22LVJ6mObtG1ij1F
|
||||
nJj381pkopQ579RT/14yT+cCgYEAoNop/ygCEp6tDAPr63HfaKDWY7Xy2B85nh62DiWqZ+eX
|
||||
hkh/+mSKryRkkQiVSOxEzR5S2G68yXeiuGHF1N0XuwveDvSRP0wNJ5XVXY3F+cI9Y/Kdt2Kj
|
||||
Hqf7Nm9bxlDHxj4LrL1PvJniG7Pk3e3eV+NIS8eNeWngEdZCrwDeRBECgYBYQM+e7qorvtpD
|
||||
xRMuv1KVt8AbfXZ+k01NmBz3aD4ZgpSMCncm7DyuXG1ANw56BnJU585F7ZAcZiQql5SQJRAJ
|
||||
4LoWhyRCYFNSWCnpXuV8dFXdfMfMOG+0O3jTLNkKPFwQ1LHfsawc98FcimkLk9iSJ+oUqMLX
|
||||
6GNuTjGzLBLbsQKBgEwglKm/HKQFVy8NmBV5clvJa04LqeBhftp3/H/bkbfVYuBZ5b/y37Qe
|
||||
jD4cCtNVhwZxKJJobo8HQQr0szWYYgfTiQgY9bk4XC10+2Nomt8m7/kitUQ6uarjo8BSmePV
|
||||
DYeki8/4VQmU5u9nh5UFZCrCVdAPpMRmuHaYqjy7M3aRAoGAVKyUCZnu9AIkmCmuPEvF/7mQ
|
||||
88zJ3y8BF44iTjKLquMb4SgK0Hur9EiPcufuzEHMiiHE/m6DPhE8OeV984ZlgwrXXcLvdnDL
|
||||
N3lVkTGSNQjRmjK66ITqyTAscc/Hi/W/LzLl0ZjlgynJjtrJQRCIv5tEAaCWeEEY9zS1rD4j
|
||||
4Jo=
|
||||
-----END RSA PRIVATE KEY-----
|
||||
7
lab12/keys/charliePub.txt
Normal file
7
lab12/keys/charliePub.txt
Normal file
@@ -0,0 +1,7 @@
|
||||
-----BEGIN RSA PUBLIC KEY-----
|
||||
MIIBCgKCAQEAh3tLOaiZvejIsA08OBMqYB4zdn5emWjcJpIHObW8H5jrcj9hpGe1IZ+QBpZ5
|
||||
3318aD0uoJPSnOLXcLQR5v8AVdddmwaJr3meaW8PL/J5BPMpDyknxk2GgvZRxXy/eQkvVLXb
|
||||
EIRzG5+8Zj8PXkc6rV+qlJEkK/0kuN2y9wz/vYN5z5Fq5qmGnZO665HBh15OxM9b30FliBA/
|
||||
DXmD+zK8oR3Gwk3W8GISWmckAqFegZQr9ocY73JHFGTXYmxVbGiWvpmEIuES57bKQX4fg3Xa
|
||||
2yQnW8jco48YX6ScPSpVWRiP+56YgAsoojHYr/Evn+fVE4OLqlLrNgWp9UWE6OKqVwIDAQAB
|
||||
-----END RSA PUBLIC KEY-----
|
||||
51
lab12/signingExample.js
Normal file
51
lab12/signingExample.js
Normal file
@@ -0,0 +1,51 @@
|
||||
var crypto = require('crypto');
|
||||
var fs = require('fs');
|
||||
var events = require('events');
|
||||
|
||||
function Signer(privKeyFile) {
|
||||
// Reads in and stores the private key
|
||||
this.privateKey = fs.readFileSync(privKeyFile).toString('ascii');
|
||||
}
|
||||
|
||||
Signer.prototype.signMessage = function(messageFileName) {
|
||||
// Read in the file
|
||||
var msg = fs.readFileSync(messageFileName).toString('ascii');
|
||||
// Specify which signing algorithm we want to use
|
||||
var sign = crypto.createSign('RSA-SHA256');
|
||||
// Sign the message and return the signature in hexadecimal form
|
||||
return sign.update(msg).sign(this.privateKey,'hex');
|
||||
}
|
||||
|
||||
|
||||
function Verifier(pubKeyFile) {
|
||||
// Reads in and stores the public key
|
||||
this.publicKey = fs.readFileSync(pubKeyFile).toString('ascii');
|
||||
}
|
||||
Verifier.prototype = new events.EventEmitter();
|
||||
|
||||
Verifier.prototype.verifySignature = function(messageFileName, signature) {
|
||||
// Read in the file
|
||||
var msg = fs.readFileSync(messageFileName).toString('ascii');
|
||||
// Test the signature
|
||||
var legit = crypto.createVerify('RSA-SHA256').update(msg)
|
||||
.verify(this.publicKey, signature, 'hex');
|
||||
if (legit) {
|
||||
console.log("Signature is valid");
|
||||
} else {
|
||||
console.log("Invalid signature");
|
||||
}
|
||||
}
|
||||
|
||||
var verifier = new Verifier('./keys/charliePub.txt');
|
||||
Verifier.prototype.on('signing', verifier.verifySignature);
|
||||
|
||||
// Valid signing
|
||||
var signer = new Signer('./keys/charliePriv.txt');
|
||||
var signature = signer.signMessage('./message.txt');
|
||||
verifier.emit('signing', './message.txt', signature);
|
||||
|
||||
// Wrong signature used
|
||||
signer = new Signer('./keys/bobPriv.txt');
|
||||
signature = signer.signMessage('./message.txt');
|
||||
verifier.emit('signing', './message.txt', signature);
|
||||
|
||||
23
lab12/tcpserver.js
Normal file
23
lab12/tcpserver.js
Normal file
@@ -0,0 +1,23 @@
|
||||
var net = require('net');
|
||||
var eol = require('os').EOL;
|
||||
|
||||
var srvr = net.createServer();
|
||||
|
||||
srvr.on('connection', function(client) {
|
||||
client.write('Hello there!' + eol);
|
||||
client.end();
|
||||
});
|
||||
|
||||
srvr.listen(9000);
|
||||
|
||||
// node tcpserver.js
|
||||
|
||||
/*
|
||||
$ telnet 127.0.0.1 9000
|
||||
Trying 127.0.0.1...
|
||||
Connected to localhost.
|
||||
Escape character is '^]'.
|
||||
Hello there!
|
||||
Connection closed by foreign host.
|
||||
*/
|
||||
|
||||
11
lab13/rabbit.js
Normal file
11
lab13/rabbit.js
Normal file
@@ -0,0 +1,11 @@
|
||||
/*global console*/
|
||||
/*jslint this*/
|
||||
var name = "Monty";
|
||||
var r = new Rabbit("Python");
|
||||
|
||||
function Rabbit(name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
console.log(r.name); // ERROR!!!
|
||||
console.log(name); // Prints "Python"
|
||||
15
lab13/rabbit.ts
Normal file
15
lab13/rabbit.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
// rabbit.ts
|
||||
|
||||
class Rabbit {
|
||||
name: string;
|
||||
|
||||
constructor(name: string) {
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
|
||||
let myName: string = "Monty";
|
||||
let r: Rabbit = new Rabbit("Python");
|
||||
|
||||
console.log(r.name); // ERROR!!!
|
||||
console.log(myName); // Prints "Python"
|
||||
20
lab13/salary.js
Normal file
20
lab13/salary.js
Normal file
@@ -0,0 +1,20 @@
|
||||
function Employee(fname, lname, salary) {
|
||||
this.fname = fname;
|
||||
this.lname = lname;
|
||||
this.salary = salary;
|
||||
}
|
||||
|
||||
var emps = [new Employee("Alice", "Alleyson", 95000),
|
||||
new Employee("Robert", "Tables", "80000"),
|
||||
new Employee("Charles", "Chaplin", 42350)];
|
||||
|
||||
function totalSalary(empList) {
|
||||
var ttl = 0;
|
||||
for (i in empList) {
|
||||
ttl += empList[i].salary;
|
||||
}
|
||||
return ttl;
|
||||
}
|
||||
|
||||
console.log(totalSalary(emps));
|
||||
|
||||
28
lab13/sortAndGetLargest.js
Normal file
28
lab13/sortAndGetLargest.js
Normal file
@@ -0,0 +1,28 @@
|
||||
/*global console*/
|
||||
/*jslint for */
|
||||
|
||||
var largest = sortAndGetLargest([99, 2, 43, 8, 0, 21, 12]);
|
||||
|
||||
function swap(arr, i, j) {
|
||||
var tmp = arr[i];
|
||||
arr[i] = arr[j];
|
||||
arr[j] = tmp;
|
||||
}
|
||||
function sortAndGetLargest(arr) {
|
||||
var tmp = arr[0]; // largest elem
|
||||
var i;
|
||||
var j;
|
||||
for (i = 0; i < arr.length; i += 1) {
|
||||
if (arr[i] > tmp) {
|
||||
tmp = arr[i];
|
||||
}
|
||||
for (j = i + 1; j < arr.length; j += 1) {
|
||||
if (arr[i] < arr[j]) {
|
||||
swap(arr, i, j);
|
||||
}
|
||||
}
|
||||
}
|
||||
return tmp;
|
||||
}
|
||||
|
||||
console.log(largest); // should be 99, but prints 0
|
||||
28
lab13/sortAndGetLargest.ts
Normal file
28
lab13/sortAndGetLargest.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
// sortAndGetLargest.ts
|
||||
|
||||
function swap(arr: number[], i: number, j: number): void {
|
||||
const tmp: number = arr[i];
|
||||
arr[i] = arr[j];
|
||||
arr[j] = tmp;
|
||||
}
|
||||
|
||||
function sortAndGetLargest(arr: number[]): number {
|
||||
if (arr.length === 0) {
|
||||
throw new Error("Array must not be empty");
|
||||
}
|
||||
|
||||
// Sort the array in descending order
|
||||
for (let i = 0; i < arr.length; i += 1) {
|
||||
for (let j = i + 1; j < arr.length; j += 1) {
|
||||
if (arr[i] < arr[j]) {
|
||||
swap(arr, i, j);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The largest element is now at index 0
|
||||
return arr[0];
|
||||
}
|
||||
|
||||
const largest: number = sortAndGetLargest([99, 2, 43, 8, 0, 21, 12]);
|
||||
console.log(largest); // should be 99, but prints 0
|
||||
1828
lab14/typed-semantics.pdf
Normal file
1828
lab14/typed-semantics.pdf
Normal file
File diff suppressed because it is too large
Load Diff
143
lab14/typed-semantics.typ
Normal file
143
lab14/typed-semantics.typ
Normal file
@@ -0,0 +1,143 @@
|
||||
/*
|
||||
In this lab, you will develop evaluation rules and typing rules for a small language.
|
||||
|
||||
Our language is an extended version of the one discussed in class:
|
||||
|
||||
e ::= true
|
||||
| false
|
||||
| i (Integers)
|
||||
| s (Strings)
|
||||
| succ e
|
||||
| pred e
|
||||
| iszero e
|
||||
| if e then e else e
|
||||
| concat e e
|
||||
| isemptystr e
|
||||
| strlen e
|
||||
|
||||
Our types must therefore include:
|
||||
|
||||
T ::= Bool
|
||||
| Int
|
||||
| String
|
||||
|
||||
First, write the evaluation order rules (using small-step semantics) for the expressions in the language.
|
||||
|
||||
Next, define the typing rules for these expressions. Be sure that your typing rules guarantee both progress and preservation. (For ways of formally guaranteeing these properties, see Chapter 8 of Ben Pierce's "Types and Programming Languages").
|
||||
*/
|
||||
|
||||
#set page(columns: 2)
|
||||
|
||||
// Language keywords rendered in monospace
|
||||
#let kw(body) = text(font: "DejaVu Sans Mono", size: 0.85em, body)
|
||||
|
||||
#let tr = kw[true]
|
||||
#let fl = kw[false]
|
||||
#let ife(e1, e2, e3) = [#kw[if] #e1 #kw[then] #e2 #kw[else] #e3]
|
||||
#let suc(e) = [#kw[succ] #e]
|
||||
#let prd(e) = [#kw[pred] #e]
|
||||
#let iszero(e) = [#kw[iszero] #e]
|
||||
#let concat(e1, e2) = [#kw[concat] #e1 #h(0.2em) #e2]
|
||||
#let isemptystr(e) = [#kw[isemptystr] #e]
|
||||
#let strlen(e) = [#kw[strlen] #e]
|
||||
|
||||
// Type keywords
|
||||
#let bool = kw[Bool]
|
||||
#let int = kw[Int]
|
||||
#let string = kw[String]
|
||||
|
||||
// Small-step evaluation relation: e -> e'
|
||||
#let sstep(e, ee) = $#e -> #ee$
|
||||
|
||||
// Typing relation: e : T
|
||||
#let tstep(e, T) = $#e : #T$
|
||||
|
||||
// Rule name label in small-caps
|
||||
#let rel(name) = text(size: 0.9em, smallcaps[\[#name\]])
|
||||
|
||||
// Rule: fraction with name on the left
|
||||
// premises (content), conclusion (content)
|
||||
#let rule(name, premises, conclusion) = {
|
||||
stack(
|
||||
dir: ttb,
|
||||
spacing: 0.4em,
|
||||
rel(name),
|
||||
box(stroke: 0.5pt, inset: 4pt, math.equation(
|
||||
block: true,
|
||||
numbering: none,
|
||||
math.frac(premises, conclusion),
|
||||
)),
|
||||
)
|
||||
v(0.8em)
|
||||
}
|
||||
|
||||
= Evaluation Rules
|
||||
|
||||
#v(0.6em)
|
||||
|
||||
#rule("E-Succ-Ctx", $#sstep[$e$][$e'$]$, $#sstep[#suc[$e$]][#suc[$e'$]]$)
|
||||
#rule("E-Succ", "", $#sstep[#suc[$i$]][$i + 1$]$)
|
||||
|
||||
#rule("E-Pred-Ctx", $#sstep[$e$][$e'$]$, $#sstep[#prd[$e$]][#prd[$e'$]]$)
|
||||
#rule("E-Pred", "", $#sstep[#prd[$i$]][$i - 1$]$)
|
||||
|
||||
#rule("E-IsZero-Ctx", $#sstep[$e$][$e'$]$, $#sstep[#iszero[$e$]][#iszero[$e'$]]$)
|
||||
#rule("E-IsZero-Zero", "", $#sstep[#iszero[0]][true]$)
|
||||
#rule("E-IsZero-NonZero", $i != 0$, $#sstep[#iszero[#suc[$i$]]][false]$)
|
||||
|
||||
#rule("E-If-Ctx", $#sstep[$e_1$][$e'_1$]$, $#sstep[#ife[$e_1$][$e_2$][$e_3$]][#ife[$e'_1$][$e_2$][$e_3$]]$)
|
||||
#rule("E-If-True", "", $#sstep[#ife[true][$e_2$][$e_3$]][$e_2$]$)
|
||||
#rule("E-If-False", "", $#sstep[#ife[false][$e_2$][$e_3$]][$e_3$]$)
|
||||
|
||||
#rule("E-Concat-Ctx1", $#sstep[$e_1$][$e'_1$]$, $#sstep[#concat[$e_1$][$e_2$]][#concat[$e'_1$][$e_2$]]$)
|
||||
#rule("E-Concat-Ctx2", $#sstep[$e_2$][$e'_2$]$, $#sstep[#concat[$s_1$][$e_2$]][#concat[$s_1$][$e'_2$]]$)
|
||||
#rule("E-Concat", $s_3 = s_1 + s_2$, $#sstep[#concat[$s_1$][$s_2$]][$s_3$]$)
|
||||
|
||||
#rule("E-IsEmptyStr-Ctx", $#sstep[$e$][$e'$]$, $#sstep[#isemptystr[$e$]][#isemptystr[$e'$]]$)
|
||||
#rule("E-IsEmptyStr-Empty", "", $#sstep[#isemptystr[`""`]][true]$)
|
||||
#rule("E-IsEmptyStr-NonEmpty", [$s !=$ `""`], $#sstep[#isemptystr[$s$]][false]$)
|
||||
|
||||
#rule("E-StrLen-Ctx", $#sstep[$e$][$e'$]$, $#sstep[#strlen[$e$]][#strlen[$e'$]]$)
|
||||
#rule("E-StrLen-Zero", "", $#sstep[#strlen[`""`]][0]$)
|
||||
#rule("E-StrLen-NonZero", [c is a single char, $s = c + s'$], $#sstep[#strlen[$s$]][#suc[#strlen[$s'$]]]$)
|
||||
|
||||
#pagebreak()
|
||||
= Typing Rules
|
||||
|
||||
#v(0.6em)
|
||||
|
||||
#rule("T-True", "", $#tstep[true][#bool]$)
|
||||
#rule("T-False", "", $#tstep[false][#bool]$)
|
||||
#rule("T-Int", "", $#tstep[n][#int]$)
|
||||
#rule("T-String", "", $#tstep[s][#string]$)
|
||||
|
||||
#rule("T-Succ", $#tstep[$e$][#int]$, $#tstep[#suc[$e$]][#int]$)
|
||||
#rule("T-Pred", $#tstep[$e$][#int]$, $#tstep[#prd[$e$]][#int]$)
|
||||
#rule("T-IsZero", $#tstep[$e$][#int]$, $#tstep[#iszero[$e$]][#bool]$)
|
||||
#rule(
|
||||
"T-If",
|
||||
[
|
||||
$#tstep[$e_1$][#bool]$,
|
||||
$#tstep[$e_2$][$T$]$,
|
||||
$#tstep[$e_3$][$T$]$
|
||||
],
|
||||
$#tstep[#ife[$e_1$][$e_2$][$e_3$]][T]$,
|
||||
)
|
||||
#rule(
|
||||
"T-Concat",
|
||||
[
|
||||
$#tstep[$e_1$][$#string$]$,
|
||||
$#tstep[$e_2$][$#string$]$
|
||||
],
|
||||
$#tstep[#concat[$e_1$][$e_2$]][#string]$,
|
||||
)
|
||||
#rule(
|
||||
"T-IsEmptyStr",
|
||||
$#tstep[$e$][$#string$]$,
|
||||
$#tstep[#isemptystr[$e$]][#bool]$,
|
||||
)
|
||||
#rule(
|
||||
"T-StrLen",
|
||||
$#tstep[$e$][$#string$]$,
|
||||
$#tstep[#strlen[$e$]][#int]$,
|
||||
)
|
||||
113
lab15/smart-array.js
Normal file
113
lab15/smart-array.js
Normal file
@@ -0,0 +1,113 @@
|
||||
"use strict";
|
||||
|
||||
// Matches paterns like '3-10'
|
||||
const RANGE_PAT = /^(\d+)-(\d+)$/;
|
||||
|
||||
// Matches negative index values
|
||||
const FROM_END_PAT = /^-(\d+)$/;
|
||||
|
||||
const NUM_PAT = /^-?\d+$/;
|
||||
|
||||
function SmartArray(...args) {
|
||||
return new Proxy(args, {
|
||||
get: function(target, prop) {
|
||||
if (prop.match(RANGE_PAT)) {
|
||||
// Return a subarray of the elements in the specified range,
|
||||
// INCLUDING the specified end index.
|
||||
let start = parseInt(prop.replace(RANGE_PAT, "$1"));
|
||||
let end = parseInt(prop.replace(RANGE_PAT, "$2")) + 1;
|
||||
return target.slice(start, end);
|
||||
} else if (prop.match(FROM_END_PAT)) {
|
||||
// Return the element at the specified position, counting
|
||||
// back from target.length. So "-1" will refer to the last
|
||||
// element in the array.
|
||||
//
|
||||
// If the resulting index position is negative,
|
||||
// raise an exception.
|
||||
let indexFromEnd = parseInt(prop.replace(FROM_END_PAT, "$1"));
|
||||
let index = target.length - indexFromEnd;
|
||||
if (index < 0) {
|
||||
throw new Error("Index out of bounds");
|
||||
}
|
||||
return Reflect.get(target, index);
|
||||
} else {
|
||||
// Do the usual array thing -- get the value at the specified index.
|
||||
return Reflect.get(...arguments);
|
||||
}
|
||||
},
|
||||
set: function(target, prop, newVal) {
|
||||
// For smart arrays, we only allow updates to numerical fields.
|
||||
// Throw an exception for a 'prop' that is not a valid integer.
|
||||
//
|
||||
// If prop is zero or positive, update the array position normally.
|
||||
//
|
||||
// If negative, update the position counting from the end of the array.
|
||||
// However, raise an exception if the resulting index is still negative.
|
||||
if (!prop.match(NUM_PAT)) {
|
||||
throw new Error("Invalid index for assignment");
|
||||
}
|
||||
let index = parseInt(prop);
|
||||
if (index < 0) {
|
||||
index = target.length + index;
|
||||
if (index < 0) {
|
||||
throw new Error("Index out of bounds");
|
||||
}
|
||||
}
|
||||
Reflect.set(target, index, newVal);
|
||||
return true;
|
||||
},
|
||||
deleteProperty: function(target, prop) {
|
||||
if (!prop.match(NUM_PAT)) {
|
||||
throw new Error("Invalid index for deletion");
|
||||
}
|
||||
let index = parseInt(prop);
|
||||
if (index < 0) {
|
||||
index = target.length + index;
|
||||
if (index < 0) {
|
||||
throw new Error("Index out of bounds");
|
||||
}
|
||||
}
|
||||
target.splice(index, 1);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let arr = SmartArray('a', 'b', 'c', 'd', 'e', 'f');
|
||||
|
||||
console.log(arr[0]); // a
|
||||
console.log(arr[4]); // e
|
||||
console.log(arr['hello']); // undefined
|
||||
|
||||
console.log(arr['2-4']); // [c,d,E]
|
||||
console.log(arr['3-5']); // [d,E,f]
|
||||
|
||||
console.log(arr[-1]); // f
|
||||
console.log(arr[-3]); // d
|
||||
|
||||
try {
|
||||
console.log(arr[-99]);
|
||||
} catch (e) {
|
||||
console.log("Exception correctly thrown.");
|
||||
}
|
||||
|
||||
arr[1] = 'B';
|
||||
console.log(arr[1]); // B
|
||||
|
||||
arr[-2] = 'E';
|
||||
console.log(arr[4]); // E
|
||||
|
||||
try {
|
||||
arr['2-4'] = 'hello';
|
||||
} catch (e) {
|
||||
console.log("Exception correctly thrown.");
|
||||
}
|
||||
|
||||
try {
|
||||
arr[3 * "hello"] = 'hello';
|
||||
} catch (e) {
|
||||
console.log("Exception correctly thrown.");
|
||||
}
|
||||
|
||||
delete arr[-2];
|
||||
console.log(arr);
|
||||
21
lab15/tracing.js
Normal file
21
lab15/tracing.js
Normal file
@@ -0,0 +1,21 @@
|
||||
let obj = { foo: 'bar' };
|
||||
|
||||
let o = new Proxy(obj, {
|
||||
set: (target, name, val) => {
|
||||
console.log(`Setting ${name} to ${val}`);
|
||||
//target[name] = val;
|
||||
//return true;
|
||||
//return Reflect.set(target,name,val);
|
||||
return Reflect.set(...arguments);
|
||||
},
|
||||
get: (target, name) => {
|
||||
console.log(`Getting ${name}`);
|
||||
return Reflect.get(...arguments);
|
||||
}
|
||||
});
|
||||
|
||||
o.foo = "fighters";
|
||||
|
||||
let ff = "foo" + o.foo;
|
||||
|
||||
|
||||
47
lab15/undoable.js
Normal file
47
lab15/undoable.js
Normal file
@@ -0,0 +1,47 @@
|
||||
"use strict";
|
||||
|
||||
// Input: any object.
|
||||
//
|
||||
// Returns: An object with a 'revert' method that reverts
|
||||
// any changes to the specified field.
|
||||
function makeUndoable(o) {
|
||||
let oldVals = {};
|
||||
return new Proxy(o, {
|
||||
get: (target, name) => {
|
||||
// Adding a 'revert' method on undoable objects,
|
||||
// which is not part of the original object.
|
||||
if (name === 'revert') {
|
||||
return function(p) {
|
||||
target[p] = oldVals[p];
|
||||
}
|
||||
// If any property other than 'revert' is requested,
|
||||
// return the value from the underlying object.
|
||||
} else return target[name];
|
||||
},
|
||||
set: (target, name, val) => {
|
||||
oldVals[name] = target[name];
|
||||
o[name] = val;
|
||||
// Returns true to indicate that assignment was successful.
|
||||
return true;
|
||||
},
|
||||
deleteProperty: (target, name) => {
|
||||
oldVals[name] = target[name];
|
||||
return delete target[name];
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
let emp = { fname: "Joe", lname: "Smith", id: 1234, salary: 100 };
|
||||
emp = makeUndoable(emp);
|
||||
|
||||
emp.fname = "Joey";
|
||||
console.log(`Joe's name accidentally changed to ${emp.fname}`);
|
||||
|
||||
emp.revert('fname');
|
||||
console.log(`Joe's name changed back to ${emp.fname}`);
|
||||
|
||||
delete emp.lname;
|
||||
console.log(`${emp.fname}'s last name is now ${emp.lname}`);
|
||||
emp.revert('lname');
|
||||
console.log(`${emp.fname}'s last name is now ${emp.lname}`);
|
||||
|
||||
88
lab16/stlc.hs
Normal file
88
lab16/stlc.hs
Normal file
@@ -0,0 +1,88 @@
|
||||
import Data.Map (Map)
|
||||
import qualified Data.Map as Map
|
||||
|
||||
-- We represent variables as strings.
|
||||
type Variable = String
|
||||
|
||||
data Expression =
|
||||
ETrue
|
||||
| EFalse
|
||||
| EInt Int
|
||||
| ESucc Expression
|
||||
| EPred Expression
|
||||
| EIsZero Expression
|
||||
| EIf Expression Expression Expression
|
||||
| ELambda Variable StlcType Expression -- (\x:T.e)
|
||||
| EApp Expression Expression
|
||||
| EVar Variable
|
||||
deriving (Show)
|
||||
|
||||
data Value =
|
||||
VTrue
|
||||
| VFalse
|
||||
| VNum Int
|
||||
deriving (Show)
|
||||
|
||||
data StlcType =
|
||||
TBool
|
||||
| TInt
|
||||
| TFun StlcType StlcType -- T1 -> T2
|
||||
deriving (Show,Eq)
|
||||
|
||||
type TypingEnv = Map Variable StlcType
|
||||
|
||||
--I'm using zero a lot, so I am making a shortcut
|
||||
zero = EInt 0
|
||||
|
||||
typecheckFail e = error $ "Expression " ++ (show e) ++ " does not typecheck"
|
||||
|
||||
typecheck :: Expression -> TypingEnv -> StlcType
|
||||
typecheck ETrue _ = TBool
|
||||
typecheck EFalse _ = TBool
|
||||
typecheck (EInt _) _ = TInt
|
||||
typecheck e@(ESucc e1) env = case typecheck e1 env of
|
||||
TInt -> TInt
|
||||
_ -> typecheckFail e
|
||||
typecheck e@(EPred e1) env = case typecheck e1 env of
|
||||
TInt -> TInt
|
||||
_ -> typecheckFail e
|
||||
typecheck e@(EIf e1 e2 e3) env =
|
||||
let t1 = typecheck e1 env
|
||||
t2 = typecheck e2 env
|
||||
t3 = typecheck e3 env
|
||||
in if t1 == TBool && t2 == t3 then t2 else typecheckFail e
|
||||
typecheck e@(EIsZero e1) env = case typecheck e1 env of
|
||||
TInt -> TBool
|
||||
_ -> typecheckFail e
|
||||
typecheck e@(EVar x) env = case Map.lookup x env of
|
||||
Just t -> t
|
||||
Nothing -> typecheckFail e
|
||||
typecheck e@(ELambda x tin e') env =
|
||||
let env' = Map.insert x tin env
|
||||
in TFun tin (typecheck e' env')
|
||||
typecheck e@(EApp e1 e2) env =
|
||||
let t1@(TFun tin tout) = typecheck e1 env
|
||||
t2 = typecheck e2 env
|
||||
in if t2 == tin then tout else typecheckFail e
|
||||
|
||||
|
||||
--Some sample cases
|
||||
test1 = typecheck (ESucc zero) Map.empty
|
||||
test2 = typecheck (EPred (ESucc zero)) Map.empty
|
||||
test3 = typecheck (EIf ETrue zero (ESucc (ESucc zero))) Map.empty
|
||||
test4 = typecheck (ELambda "x" TInt ETrue) Map.empty
|
||||
test5 = typecheck (EApp (ELambda "x" TInt (EIf (EIsZero (EVar "x")) (ESucc zero) zero)) (ESucc zero)) Map.empty
|
||||
|
||||
bad1 = typecheck (ESucc EFalse) Map.empty
|
||||
bad2 = typecheck (EApp (ELambda "x" TInt (EIsZero (EVar "x"))) ETrue) Map.empty
|
||||
|
||||
-- main
|
||||
|
||||
main = do
|
||||
print test1
|
||||
print test2
|
||||
print test3
|
||||
print test4
|
||||
print test5
|
||||
print bad1
|
||||
print bad2
|
||||
326
lab17/compiler.js
Normal file
326
lab17/compiler.js
Normal file
@@ -0,0 +1,326 @@
|
||||
"use strict";
|
||||
|
||||
const fs = require('fs');
|
||||
|
||||
const opcodes = require('./op-codes.js').opcodes;
|
||||
|
||||
const MAX_BUFF_SIZE = 256;
|
||||
|
||||
// Constants for types
|
||||
const LIST = 1;
|
||||
const OP = 2;
|
||||
const NUM = 3;
|
||||
const BOOL = 4;
|
||||
const VAR = 5;
|
||||
|
||||
/**
|
||||
* The Compiler class is responsible for taking a .scm
|
||||
* text file and converting it into bytecode format.
|
||||
*/
|
||||
class Compiler {
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
constructor() {
|
||||
this.buildMnemonicLookup();
|
||||
this.varMap = {};
|
||||
this.varOffset = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tokenizes a Scheme file, stripping out any comments.
|
||||
*
|
||||
* @param {String} contents - Scheme file, as text.
|
||||
*
|
||||
* @returns {[String]} - Array of tokens, represented as strings.
|
||||
*/
|
||||
tokenize(contents) {
|
||||
let lines = contents.trim().split('\n')
|
||||
let tokens = [];
|
||||
lines.forEach((ln) => {
|
||||
// Ensuring that parens are always surrounded
|
||||
// by spaces to simplify parsing.
|
||||
ln = ln.replaceAll("(", " ( ")
|
||||
.replaceAll(")", " ) ");
|
||||
|
||||
// The comment character in Scheme is ';'
|
||||
ln = ln.replace(/;.*/, "");
|
||||
|
||||
tokens.push(...ln.split(/\s+/).filter(s=>s.length!==0));
|
||||
});
|
||||
return tokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a stream of tokens, returning an array of objects
|
||||
* representing the top-level Scheme lists in the program.
|
||||
* (Note that in Scheme, a list is treated as a function call.)
|
||||
*
|
||||
* @param {[String]} tokens - An array of tokens.
|
||||
*
|
||||
* @returns {[Object]} - The AST, as a JS object literal.
|
||||
*/
|
||||
parse(tokens) {
|
||||
// The top level AST does not have a type.
|
||||
let ast = { children: []};
|
||||
for (let i=0; i<tokens.length; i++) {
|
||||
let tok = tokens[i];
|
||||
if (tok === "(") {
|
||||
let newAst = { parent: ast, type: LIST, children: [] };
|
||||
ast.children.push(newAst);
|
||||
ast = newAst;
|
||||
} else if (tok === ")") {
|
||||
ast = ast.parent;
|
||||
} else if (tok.match(/^\d+$/)) {
|
||||
ast.children.push({ type: NUM, value: parseInt(tok) });
|
||||
} else if (tok === "#t") {
|
||||
ast.children.push({ type: BOOL, value: true });
|
||||
} else if (tok === "#f") {
|
||||
ast.children.push({ type: BOOL, value: false });
|
||||
} else if (tok.match(/^\w+$/)) {
|
||||
ast.children.push({ type: VAR, value: tok });
|
||||
} else {
|
||||
ast.children.push({ type: OP, value: tok})
|
||||
}
|
||||
}
|
||||
return ast.children;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints out an AST, filtering out circular references.
|
||||
*
|
||||
* @param {Object} ast - The AST to print.
|
||||
*/
|
||||
printAST(ast) {
|
||||
console.log(`AST is ${JSON.stringify(ast, (key, value) => {
|
||||
if (key === 'parent') return value.id;
|
||||
else return value;
|
||||
})}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a byte to the next position in the bytecode buffer,
|
||||
* updating the offset to the position for the new write.
|
||||
*
|
||||
* @param {Number} byte - A valid byte.
|
||||
*/
|
||||
writeByte(byte) {
|
||||
this.offset = this.bytecode.writeUInt8(byte, this.offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks up the opcode by its mnemonic and writes it to
|
||||
* the bytecode buffer.
|
||||
*
|
||||
* @param {String} mnemonic - The mnemonic for the opcode.
|
||||
*/
|
||||
writeOp(mnemonic) {
|
||||
let opcode = this.lookupTable[mnemonic];
|
||||
if (opcode === undefined) {
|
||||
throw new Error(`The mnemonic ${mnemonic} is not defined.`);
|
||||
}
|
||||
this.writeByte(this.lookupTable[mnemonic]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts AST into binary bytecode.
|
||||
*
|
||||
* @param {Object} ast - abstract syntax tree of program.
|
||||
*/
|
||||
writeBytecode(ast) {
|
||||
if (ast.type === NUM) {
|
||||
// Numbers are just pushed on to the stack.
|
||||
this.writeOp('PUSH1');
|
||||
this.writeByte(ast.value);
|
||||
return;
|
||||
} else if (ast.type === BOOL) {
|
||||
// Booleans will be stored as either 1 for true, or as a 0 for false.
|
||||
this.writeOp('PUSH1');
|
||||
this.writeByte(ast.value ? 1 : 0);
|
||||
return;
|
||||
} else if (ast.type === VAR) {
|
||||
// We look up the offset for a variable and push the offset
|
||||
// value on to the stack. The 'MLOAD' operation will
|
||||
// retrieve the value stored at that position in the memory.
|
||||
this.writeOp('PUSH1');
|
||||
this.writeByte(this.varMap[ast.value]);
|
||||
this.writeOp('MLOAD');
|
||||
return;
|
||||
}
|
||||
|
||||
// If we made it hear, we have a list.
|
||||
// The first argument is the name of the 'function'
|
||||
// that we will be invoking.
|
||||
let first = ast.children[0];
|
||||
|
||||
// Almost all functions need some special handling for the
|
||||
// first argument. Some functions will need the additional
|
||||
// arguments stored in 'rest'.
|
||||
let second = ast.children[1];
|
||||
let rest = ast.children.slice(2);
|
||||
|
||||
switch (first.value) {
|
||||
case "println":
|
||||
this.writeBytecode(second);
|
||||
this.writeOp('PRINT');
|
||||
break;
|
||||
|
||||
case "define":
|
||||
// The define function lets us store variables.
|
||||
//
|
||||
// The variable name is stored in 'second.value'.
|
||||
// Update the 'this.varMap' array to store the current
|
||||
// value of 'this.varOffset'.
|
||||
//
|
||||
// The VM will need to push the value on to the stack,
|
||||
// push 'this.varOffset' on to the stack, and then
|
||||
// invoke 'MSTORE'.
|
||||
//
|
||||
// Increment this.varOffset so that it points to the next
|
||||
// position in memory.
|
||||
this.varMap[second.value] = this.varOffset;
|
||||
rest.forEach((x) => {
|
||||
this.writeBytecode(x);
|
||||
});
|
||||
this.writeOp('PUSH1');
|
||||
this.writeByte(this.varOffset);
|
||||
this.writeOp('MSTORE');
|
||||
this.varOffset++;
|
||||
break;
|
||||
|
||||
case "if":
|
||||
// EXTRA CREDIT!
|
||||
// Add support for if expressions.
|
||||
// The cond.scm file gives you some good examples.
|
||||
this.writeBytecode(second);
|
||||
|
||||
// get the offset program counter to change it later
|
||||
this.writeOp('PUSH1');
|
||||
const counterOffset = this.offset;
|
||||
this.writeByte(0x00);
|
||||
this.writeOp('JUMPI');
|
||||
|
||||
// else
|
||||
this.writeBytecode(rest[1]);
|
||||
// change this to after the then
|
||||
this.writeOp('PUSH1');
|
||||
const elseOffset = this.offset;
|
||||
this.writeByte(0x00);
|
||||
this.writeOp('JUMP');
|
||||
|
||||
// then
|
||||
const thenOffset = this.offset;
|
||||
this.writeOp('JUMPDEST');
|
||||
this.writeBytecode(rest[0]);
|
||||
const endOffset = this.offset;
|
||||
this.writeOp('JUMPDEST');
|
||||
|
||||
// update the offsets
|
||||
this.bytecode[counterOffset] = thenOffset;
|
||||
this.bytecode[elseOffset] = endOffset;
|
||||
break;
|
||||
|
||||
case "+":
|
||||
this.writeBytecode(second);
|
||||
rest.forEach((x) => {
|
||||
this.writeBytecode(x);
|
||||
this.writeOp('ADD');
|
||||
});
|
||||
break;
|
||||
|
||||
case "*":
|
||||
// Using the '+' case as a template, add support
|
||||
// for '*'. Note that the 'MUL' opcode only works
|
||||
// with two arguments, whereas '*' allows an arbitrary
|
||||
// number of arguments.
|
||||
this.writeBytecode(second);
|
||||
rest.forEach((x) => {
|
||||
this.writeBytecode(x);
|
||||
this.writeOp('MUL');
|
||||
});
|
||||
break;
|
||||
|
||||
case "-":
|
||||
// Add support for '-'. The approach here will be
|
||||
// Similar to the solution for '+' and '*'. However,
|
||||
// one key difference is that the order of the arguments
|
||||
// matters. You will need to use 'SWAP1' to get the
|
||||
// arguments ordered correctly before invoking 'SUB'.
|
||||
this.writeBytecode(second);
|
||||
rest.forEach((x) => {
|
||||
this.writeBytecode(x);
|
||||
this.writeOp('SWAP1');
|
||||
this.writeOp('SUB');
|
||||
});
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Error(`Unexpected head: '${first.value}'`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds up a mapping of opcode mnemonics to the corresponding
|
||||
* hexadecimal values.
|
||||
*/
|
||||
buildMnemonicLookup() {
|
||||
this.lookupTable = {};
|
||||
Object.keys(opcodes).forEach((opcode) => {
|
||||
let inst = opcodes[opcode];
|
||||
this.lookupTable[inst.mnemonic] = opcode;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This method takes a scheme file, tokenizes and parses it,
|
||||
* and finally compiles it to binary bytecode.
|
||||
*
|
||||
* @param {String} fileName - The name of the scheme file.
|
||||
*
|
||||
* @returns {String} - The name of the bytecode file.
|
||||
*/
|
||||
compileScheme(fileName) {
|
||||
if (!fileName.toLowerCase().endsWith('.scm')) {
|
||||
throw new Error(`${fileName} does not end with a .scm extension.`);
|
||||
}
|
||||
|
||||
fs.readFileSync(fileName);
|
||||
let contents = fs.readFileSync(fileName, 'utf8');
|
||||
|
||||
let tokens = this.tokenize(contents);
|
||||
let asts = this.parse(tokens);
|
||||
|
||||
// The bytecode size cannot be larger than MAX_BUFF_SIZE.
|
||||
this.bytecode = Buffer.alloc(MAX_BUFF_SIZE);
|
||||
// The offset tracks the current position in the bytecode buffer.
|
||||
this.offset = 0;
|
||||
|
||||
asts.forEach((ast) => {
|
||||
this.writeBytecode(ast);
|
||||
});
|
||||
|
||||
// The output file will have the same name as the input file,
|
||||
// except that '.scm' will be replaced with '.byco'.
|
||||
let outputFile = fileName.replace(/.scm\b/i, ".byco");
|
||||
fs.writeFileSync(outputFile, this.bytecode.slice(0, this.offset, 'hex'));
|
||||
|
||||
return outputFile;
|
||||
}
|
||||
}
|
||||
|
||||
// Handling command line arguments.
|
||||
if (process.argv0 === 'node') {
|
||||
process.argv.shift();
|
||||
}
|
||||
if (process.argv.length !== 2) {
|
||||
console.log("compiler.js <scheme file>");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
let cmplr = new Compiler();
|
||||
let scmFile = process.argv[1];
|
||||
|
||||
console.log(`Compiling ${scmFile}...`);
|
||||
let bytecodeFile = cmplr.compileScheme(scmFile);
|
||||
|
||||
console.log(`Bytecode written to ${bytecodeFile}.`);
|
||||
BIN
lab17/cond.byco
Normal file
BIN
lab17/cond.byco
Normal file
Binary file not shown.
16
lab17/cond.scm
Normal file
16
lab17/cond.scm
Normal file
@@ -0,0 +1,16 @@
|
||||
(println #t)
|
||||
(if #t (println 3) (println 4))
|
||||
(if #f (println 3) (println 4))
|
||||
|
||||
; PUSH1 1
|
||||
; PUSH1 offset_to_then
|
||||
; JUMPI
|
||||
;
|
||||
; #else
|
||||
; PUSH1 offset_to_end
|
||||
; JUMP
|
||||
;
|
||||
; JUMPDEST @offset_to_then
|
||||
; #then
|
||||
;
|
||||
; JUMPDEST @offset_to_end
|
||||
64
lab17/op-codes.js
Normal file
64
lab17/op-codes.js
Normal file
@@ -0,0 +1,64 @@
|
||||
'use strict';
|
||||
|
||||
let opcodes = {
|
||||
0x01: { mnemonic: 'ADD', evaluate: (vm) => {
|
||||
let v1 = vm.stack.pop();
|
||||
let v2 = vm.stack.pop();
|
||||
vm.stack.push(v1+v2);
|
||||
}},
|
||||
0x02: { mnemonic: 'MUL', evaluate: (vm) => {
|
||||
// Pop the top two arguments off of the stack,
|
||||
// and then push the result on to the stack.
|
||||
let v1 = vm.stack.pop();
|
||||
let v2 = vm.stack.pop();
|
||||
vm.stack.push(v1*v2);
|
||||
}},
|
||||
0x03: { mnemonic: 'SUB', evaluate: (vm) => {
|
||||
let v1 = vm.stack.pop();
|
||||
let v2 = vm.stack.pop();
|
||||
vm.stack.push(v1-v2);
|
||||
}},
|
||||
0x52: { mnemonic: 'MLOAD', evaluate: (vm) => {
|
||||
let offset = vm.stack.pop();
|
||||
vm.stack.push(vm.memory[offset]);
|
||||
}},
|
||||
0x53: { mnemonic: 'MSTORE', evaluate: (vm) => {
|
||||
let offset = vm.stack.pop();
|
||||
let value = vm.stack.pop();
|
||||
vm.memory[offset] = value;
|
||||
}},
|
||||
0x56: { mnemonic: 'JUMP', evaluate: (vm) => {
|
||||
let offset = vm.stack.pop();
|
||||
vm.pc = offset;
|
||||
}},
|
||||
0x57: { mnemonic: 'JUMPI', evaluate: (vm) => {
|
||||
let offset = vm.stack.pop();
|
||||
let condition = vm.stack.pop();
|
||||
if (condition) {
|
||||
vm.pc = offset;
|
||||
}
|
||||
}},
|
||||
0x5B: { mnemonic: 'JUMPDEST', evaluate: (vm) => {
|
||||
// Does nothing. We could check to make sure that jumps
|
||||
// always land at JUMPDEST opcodes, but it is not totally
|
||||
// clear that it is worth the bother.
|
||||
}},
|
||||
0x60: { mnemonic: 'PUSH1', evaluate: (vm) => {
|
||||
// The next byte is data, not another instruction
|
||||
vm.pc++;
|
||||
let v = vm.bytecode.readUInt8(vm.pc);
|
||||
vm.stack.push(v);
|
||||
}},
|
||||
0x90: { mnemonic: 'SWAP1', evaluate: (vm) => {
|
||||
let v1 = vm.stack.pop();
|
||||
let v2 = vm.stack.pop();
|
||||
vm.stack.push(v1);
|
||||
vm.stack.push(v2);
|
||||
}},
|
||||
0x0c: { mnemonic: 'PRINT', evaluate: (vm) => {
|
||||
// **NOTE**: This is not a real EVM opcode.
|
||||
console.log(vm.stack.pop());
|
||||
}},
|
||||
};
|
||||
|
||||
exports.opcodes = opcodes;
|
||||
1
lab17/print.byco
Normal file
1
lab17/print.byco
Normal file
@@ -0,0 +1 @@
|
||||
`
|
||||
3
lab17/print.scm
Normal file
3
lab17/print.scm
Normal file
@@ -0,0 +1,3 @@
|
||||
; Simple test that prints 3.
|
||||
(println 3)
|
||||
|
||||
BIN
lab17/store.byco
Normal file
BIN
lab17/store.byco
Normal file
Binary file not shown.
5
lab17/store.scm
Normal file
5
lab17/store.scm
Normal file
@@ -0,0 +1,5 @@
|
||||
(define x 3)
|
||||
(define y (+ x 1))
|
||||
(println (- x 2))
|
||||
(println (* x y))
|
||||
|
||||
1
lab17/test1.byco
Normal file
1
lab17/test1.byco
Normal file
@@ -0,0 +1 @@
|
||||
```
|
||||
2
lab17/test1.scm
Normal file
2
lab17/test1.scm
Normal file
@@ -0,0 +1,2 @@
|
||||
(println (+ 1 2 3))
|
||||
|
||||
2
lab17/test2.byco
Normal file
2
lab17/test2.byco
Normal file
@@ -0,0 +1,2 @@
|
||||
````
|
||||
``<02>`
|
||||
4
lab17/test2.scm
Normal file
4
lab17/test2.scm
Normal file
@@ -0,0 +1,4 @@
|
||||
(println (+ 2 3 4))
|
||||
(println (- 13 (* 2 4)))
|
||||
(println (- 10 4 3))
|
||||
|
||||
68
lab17/vm.js
Normal file
68
lab17/vm.js
Normal file
@@ -0,0 +1,68 @@
|
||||
'use strict';
|
||||
|
||||
const fs = require('fs');
|
||||
const opcodes = require('./op-codes.js').opcodes;
|
||||
|
||||
/**
|
||||
* The VM is responsible for executing the bytecode format.
|
||||
*/
|
||||
class VirtualMachine {
|
||||
|
||||
/**
|
||||
* Initializes the virtual machine with the specified amount of
|
||||
* gas. The stack and memory are both initially empty.
|
||||
*
|
||||
* @param {Number} gas - Amount of gas the VM begins with.
|
||||
*/
|
||||
constructor() {
|
||||
this.stack = [];
|
||||
this.memory = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a bytecode file and returns an array of strings,
|
||||
* which are the commands within the file.
|
||||
*/
|
||||
static loadBytecode(bytecodeFile) {
|
||||
let contents = fs.readFileSync(bytecodeFile);
|
||||
return Buffer.from(contents);
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluates the specified file.
|
||||
*/
|
||||
evaluate(bytecodeFile) {
|
||||
this.bytecode = this.constructor.loadBytecode(bytecodeFile);
|
||||
|
||||
// Initializing the program counter to keep track of our
|
||||
// place within the program.
|
||||
this.pc = 0;
|
||||
|
||||
while (this.pc < this.bytecode.length) {
|
||||
let opcode = this.bytecode.readUInt8(this.pc);
|
||||
//console.log(`Evaluating ${opcode.toString(16)}`);
|
||||
let operation = opcodes[opcode];
|
||||
if (operation === undefined) {
|
||||
throw new Error(`Unable to find instruction for ${opcode.toString(16)}`);
|
||||
}
|
||||
operation.evaluate(this);
|
||||
|
||||
this.pc++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handling command line arguments.
|
||||
if (process.argv0 === 'node') {
|
||||
process.argv.shift();
|
||||
}
|
||||
if (process.argv.length !== 2) {
|
||||
console.log("vm.js <bytecode file>");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
let bytecodeFile = process.argv[1];
|
||||
|
||||
console.log(`Executing ${bytecodeFile}...`);
|
||||
let vm = new VirtualMachine();
|
||||
vm.evaluate(bytecodeFile);
|
||||
114
lab18/eliza.rb
Normal file
114
lab18/eliza.rb
Normal file
@@ -0,0 +1,114 @@
|
||||
#! /usr/bin/env ruby -w
|
||||
|
||||
#Represents a Rogerian psychiatrist
|
||||
class Shrink
|
||||
|
||||
#initializes 'memory' of Eliza.
|
||||
def initialize()
|
||||
@he="he"
|
||||
@she="she"
|
||||
@they="they"
|
||||
end
|
||||
|
||||
#read a statement and convert it to a psychiatric response.
|
||||
def generateResponse(blather)
|
||||
#downcase for ease of substitution
|
||||
blather = blather.downcase
|
||||
|
||||
# filter out words like "well" or "perhaps" from the beginning
|
||||
blather.gsub!(/^(well|perhaps|anyway|actually),?\s+/i, '')
|
||||
|
||||
# change 'you', 'your', etc. to uppercase 'I', 'MY'
|
||||
blather.gsub!(/\byour\b/,"MY")
|
||||
blather.gsub!(/\byou\b/,'I')
|
||||
|
||||
#Replace 'my' with 'your', 'me' with 'you', 'I' with 'you', etc.
|
||||
blather.gsub!(/\bmy\b/,"your")
|
||||
blather.gsub!(/\bme\b/,"you")
|
||||
blather.gsub!(/\bi\b/,'you')
|
||||
|
||||
#Get future references -- note that these do NOT change the immediate output
|
||||
hePat=/.*\b(your (father|brother|(ex-?)?(husband|boyfriend)))\b.*/
|
||||
shePat = /.*\b(your (mother|sister|(ex-?)?(wife|girlfriend)))\b.*/
|
||||
theyPat = /.*\b(your (parents|friends|siblings|colleagues|classmates))\b.*/
|
||||
@he=blather.sub(hePat, '\1').chomp if blather =~ hePat
|
||||
@she=blather.sub(shePat, '\1').chomp if blather =~ shePat
|
||||
@they=blather.sub(theyPat, '\1').chomp if blather =~ theyPat
|
||||
|
||||
# handle "always" and "never" responses
|
||||
if blather =~ /\b(always|never)\b/
|
||||
return "CAN YOU BE MORE SPECIFIC?"
|
||||
end
|
||||
|
||||
# handle "are you" questions
|
||||
if blather =~ /^are I (.+?)\?*$/
|
||||
return "IS IT IMPORTANT IF I AM #{$1.upcase}?"
|
||||
end
|
||||
|
||||
#Sub in past references, but only for the 1st occurrence or it looks weird
|
||||
blather.sub!(/\b(he|him)\b/, @he)
|
||||
blather.sub!(/\b(she|her)\b/, @she)
|
||||
blather.sub!(/\bthey\b/, @they)
|
||||
|
||||
#Deal with name
|
||||
namePat=/.*\byour name is (\w+).*/
|
||||
@name=blather.sub(namePat,'\1')
|
||||
blather.sub!(namePat,'nice to meet you, \1. How can I help you')
|
||||
|
||||
# add some empathy for sad words
|
||||
if blather =~ /\b(sad|depressed|upset|hurt|angry|frustrated)\b/
|
||||
empathetic_responses = [
|
||||
"THAT SOUNDS DIFFICULT. ",
|
||||
"I CAN HEAR THE PAIN IN YOUR WORDS. ",
|
||||
"THAT MUST BE HARD FOR YOU. "
|
||||
]
|
||||
prefix = empathetic_responses.sample
|
||||
return prefix + "TELL ME MORE ABOUT " + blather.upcase + "?"
|
||||
end
|
||||
|
||||
#results are uppercased, for aesthetics.
|
||||
return blather.upcase + "?"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
#main -- reads from standard input unless -test is the first parameter.
|
||||
eliza = Shrink.new()
|
||||
if ARGV[0] == "-test"
|
||||
['My girlfriend never listens to me',
|
||||
"I think she might be deaf",
|
||||
"yes",
|
||||
"I am afraid of clowns",
|
||||
"Well, they just seem creepy",
|
||||
"Also, when I was a kid, a clown killed my dad",
|
||||
"Are you a clown in disguise?",
|
||||
# test "always" response
|
||||
"I always feel nervous",
|
||||
# test "never" response
|
||||
"My parents never understand me",
|
||||
# test "Are you" questions
|
||||
"Are you listening to me?",
|
||||
"Are you a real doctor?",
|
||||
# test word filtering
|
||||
"Perhaps my friends don't like me",
|
||||
"Well, I suppose that makes sense",
|
||||
"Actually, I think I'm getting better",
|
||||
# test "they" memory
|
||||
"My colleagues are very competitive",
|
||||
"They make me feel inadequate",
|
||||
"Sometimes they ignore my ideas",
|
||||
# Test empathetic responses
|
||||
"I am feeling very sad today",
|
||||
"My boss makes me frustrated",
|
||||
"I feel so depressed lately",
|
||||
].each do |stmt|
|
||||
puts stmt
|
||||
puts eliza.generateResponse(stmt)
|
||||
puts
|
||||
end
|
||||
else
|
||||
while line = gets
|
||||
response = eliza.generateResponse line
|
||||
puts response
|
||||
end
|
||||
end
|
||||
52
lab19/block.rb
Normal file
52
lab19/block.rb
Normal file
@@ -0,0 +1,52 @@
|
||||
def do_noisy
|
||||
puts "About to call block"
|
||||
yield
|
||||
puts "Just called block"
|
||||
end
|
||||
|
||||
do_noisy do
|
||||
puts 3 + 4
|
||||
end
|
||||
|
||||
|
||||
class Array
|
||||
def each_downcase
|
||||
self.each do |word|
|
||||
yield word.downcase
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
["Alpha", "Beta", "AndSoOn"].each_downcase do |word|
|
||||
puts word
|
||||
end
|
||||
|
||||
def iff b
|
||||
return if b == false
|
||||
yield
|
||||
end
|
||||
|
||||
def test_block
|
||||
iff true do
|
||||
puts "hello block"
|
||||
return
|
||||
end
|
||||
puts "goodbye block"
|
||||
end
|
||||
|
||||
def iff2 b, lam
|
||||
return if b == false
|
||||
lam.call
|
||||
end
|
||||
|
||||
def test_lambda
|
||||
iff2 true, (lambda do
|
||||
puts "hello lambda"
|
||||
return
|
||||
end)
|
||||
puts "goodbye lambda"
|
||||
end
|
||||
|
||||
test_block
|
||||
test_lambda
|
||||
|
||||
25
lab19/conversion.rb
Normal file
25
lab19/conversion.rb
Normal file
@@ -0,0 +1,25 @@
|
||||
def conversion_chart(from_units, to_units, values)
|
||||
puts "#{from_units}\t#{to_units}"
|
||||
left_line = right_line = ""
|
||||
from_units.length.times { left_line += '-' }
|
||||
to_units.length.times { right_line += '-' }
|
||||
puts "#{left_line}\t#{right_line}"
|
||||
for val in values
|
||||
converted = yield val
|
||||
puts "#{val}\t#{converted}"
|
||||
end
|
||||
puts
|
||||
end
|
||||
|
||||
celsius_temps = [0,10,20,30,40,50,60,70,80,90,100]
|
||||
conversion_chart("C", "F", celsius_temps) {|cel| cel * 9 / 5 + 32}
|
||||
|
||||
fahrenheit_temps = [0,10,20,30,40,50,60,70,80,90,100,110,120,130,140,150,160,170,180,190,200 ]
|
||||
conversion_chart("Fahr.", "Celsius", fahrenheit_temps) {|fahr| (fahr-32) * 5 / 9 }
|
||||
|
||||
|
||||
conversion_chart("Km", "Miles", (1..10)) do |km|
|
||||
mile = 0.621371 * km
|
||||
end
|
||||
|
||||
|
||||
42
lab19/emp.rb
Normal file
42
lab19/emp.rb
Normal file
@@ -0,0 +1,42 @@
|
||||
class Employee
|
||||
# Adding methods to the singleton class of the object
|
||||
# Employee (which has the class Class).
|
||||
class << self
|
||||
def add(emp)
|
||||
puts "Adding #{emp}"
|
||||
@employees = Hash.new unless @employees
|
||||
@employees[emp.name] = emp
|
||||
end
|
||||
def get_emp_by_name name
|
||||
@employees[name]
|
||||
end
|
||||
end
|
||||
#####################################################
|
||||
attr_accessor :name, :ssid, :salary
|
||||
def initialize(name, ssid, salary)
|
||||
@name = name
|
||||
@ssid = ssid
|
||||
@salary = salary
|
||||
Employee.add self
|
||||
end
|
||||
def to_s
|
||||
@name
|
||||
end
|
||||
end
|
||||
|
||||
alice = Employee.new("Alice Alley", 1234, 75000);
|
||||
bob = Employee.new("Robert Tables", 5678, 50000);
|
||||
|
||||
class << bob
|
||||
def signing_bonus
|
||||
2000
|
||||
end
|
||||
end
|
||||
|
||||
puts(bob.signing_bonus);
|
||||
#puts(alice.signing_bonus);
|
||||
|
||||
b = Employee.get_emp_by_name "Robert Tables"
|
||||
puts b.signing_bonus
|
||||
|
||||
|
||||
19
lab19/eval/class-eval-example.rb
Normal file
19
lab19/eval/class-eval-example.rb
Normal file
@@ -0,0 +1,19 @@
|
||||
class Class
|
||||
def my_attr_accessor(*args)
|
||||
args.each do |prop|
|
||||
# Creating the getter
|
||||
self.class_eval("def #{prop}; @#{prop}; end")
|
||||
# Creating the setter
|
||||
self.class_eval("def #{prop}=(v); @#{prop}=v; end")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class Musician
|
||||
my_attr_accessor :name, :genre, :instrument
|
||||
end
|
||||
|
||||
m = Musician.new
|
||||
m.name = "Norah Jones"
|
||||
puts m.name
|
||||
|
||||
21
lab19/eval/eval.js
Normal file
21
lab19/eval/eval.js
Normal file
@@ -0,0 +1,21 @@
|
||||
// Assume that this string comes from across the network
|
||||
// representing employee records.
|
||||
var jsonStr =
|
||||
"[{name: 'Philip J. Fry', age: 1000, job: 'delivery boy'}," +
|
||||
" {name: (function(){console.log('***All glory to the Hypnotoad!***')})() }," +
|
||||
" {name: 'Bender Rodriguez', age: 42, job: 'bending unit'}]";
|
||||
|
||||
var employeeRecords = eval(jsonStr);
|
||||
for (var i in employeeRecords) {
|
||||
var emp = employeeRecords[i];
|
||||
console.log(emp.name);
|
||||
}
|
||||
|
||||
/*
|
||||
$ node eval.js
|
||||
***All glory to the Hypnotoad!***
|
||||
Philip J. Fry
|
||||
undefined
|
||||
Bender Rodriguez
|
||||
*/
|
||||
|
||||
67
lab19/eval/eval.rb
Normal file
67
lab19/eval/eval.rb
Normal file
@@ -0,0 +1,67 @@
|
||||
# Ruby has rich metaprogramming tools, often bowered from Smalltalk.
|
||||
# Like JavaScript, it has an eval feature.
|
||||
prog = "puts 3 + 4"
|
||||
eval prog
|
||||
|
||||
# Eval is one of the most powerful metaprogramming features,
|
||||
# but it is also one of the most dangerous.
|
||||
print "Please enter your method name: "
|
||||
m = gets.chomp
|
||||
eval "def #{m}; puts 'Hi!'; end"
|
||||
eval m
|
||||
|
||||
=begin
|
||||
Thomass-MacBook-Pro-3:lab15 taustin$ ruby eval.rb
|
||||
Please enter your method name: abc; end; puts "Mwah, hah, hah!"; #
|
||||
Mwah, hah, hah!
|
||||
eval.rb:11: (eval):1: compile error (SyntaxError)
|
||||
(eval):1: syntax error, unexpected kEND, expecting $end
|
||||
abc; end; puts "Mwah, hah, hah!"; #
|
||||
-----------------------
|
||||
Eval is horribly abused (Richards et al. 2011, See the Eval that men do,), but it is useful at times.
|
||||
For instance, in JavaScript, it served as an early (but unsafe) version of JSON.parse.
|
||||
Similar to goto, it is a powerful but dangerous construct, and is often used in places
|
||||
where the language is missing a key feature.
|
||||
"A design pattern is the sincerest form of feature request".
|
||||
|
||||
However, it does not tend to show up as often in Ruby.
|
||||
In part, Ruby has some safer alternatives that are nearly as powerful.
|
||||
They take blocks rather than expressions.
|
||||
=end
|
||||
|
||||
# instance_eval -- used for prying open objects to get at their private data.
|
||||
# This can be handy for things like writing a debugger.
|
||||
class Person
|
||||
attr_reader :name
|
||||
def initialize name
|
||||
@name = name
|
||||
end
|
||||
end
|
||||
|
||||
bob = Person.new "Robert"
|
||||
puts bob.name
|
||||
#bob.name = "Bobby" # Error
|
||||
|
||||
bob.instance_eval do
|
||||
@name = "Bobby"
|
||||
end
|
||||
puts bob.name
|
||||
|
||||
# And finally class_eval/module_eval, which serve as an alternate way of opening up a class.
|
||||
favorite_song = "Streets of Laredo"
|
||||
class Person
|
||||
#puts favorite_song # error
|
||||
end
|
||||
|
||||
Person.class_eval do
|
||||
puts favorite_song
|
||||
#def sing
|
||||
# puts "When #{@name} went out in the #{favorite_song}..." # Will not see favorite_song
|
||||
#end
|
||||
define_method "sing" do
|
||||
puts "When #{@name} went out in the #{favorite_song}..."
|
||||
end
|
||||
end
|
||||
|
||||
bob.sing
|
||||
|
||||
30
lab19/eval/taint.rb
Normal file
30
lab19/eval/taint.rb
Normal file
@@ -0,0 +1,30 @@
|
||||
=begin
|
||||
Update the Record class so that updates with either
|
||||
a tainted name or a tainted value are ignored.
|
||||
Do this first by explicitly checking the taint on a field.
|
||||
|
||||
Would this be sufficient if an attacker could control part of the code?
|
||||
If not, how could the different taint modes be useful?
|
||||
=end
|
||||
|
||||
class Record
|
||||
def initialize fields
|
||||
@fields = fields
|
||||
end
|
||||
|
||||
def set_property name, value
|
||||
@fields[name] = value
|
||||
end
|
||||
def get_property name
|
||||
@fields[name]
|
||||
end
|
||||
end
|
||||
|
||||
r = Record.new 'fname' => 'Rick', 'lname' => 'Grimes', 'profession' => 'Police Officer'
|
||||
r.set_property 'profession'.taint, 'Zombie Hunter'
|
||||
r.set_property 'lname', 'Smith'.taint
|
||||
|
||||
p r.get_property 'profession'
|
||||
p r.get_property 'lname'
|
||||
|
||||
|
||||
21
lab19/method-missing.rb
Normal file
21
lab19/method-missing.rb
Normal file
@@ -0,0 +1,21 @@
|
||||
class Person
|
||||
attr_accessor :name
|
||||
def initialize(name)
|
||||
@name = name
|
||||
end
|
||||
|
||||
def make_introduction
|
||||
puts "Hi, my name is #{@name}. Nice to meet you."
|
||||
end
|
||||
|
||||
def method_missing(m)
|
||||
puts "Didn't understand #{m}"
|
||||
end
|
||||
end
|
||||
|
||||
alice = Person.new('Alice')
|
||||
|
||||
alice.make_introduction
|
||||
alice.foo
|
||||
|
||||
|
||||
37
lab19/record.rb
Normal file
37
lab19/record.rb
Normal file
@@ -0,0 +1,37 @@
|
||||
=begin
|
||||
Ruby provides a number of hooks that allow a developer
|
||||
to change the behavior of objects.
|
||||
The design is similar to JavaScript's Proxies (both are
|
||||
metaobject protocols), but unlike proxies,
|
||||
these hooks apply for all objects.
|
||||
|
||||
The most famous of these is method_missing,
|
||||
based on Smalltalk's doesNotUnderstand:.
|
||||
=end
|
||||
|
||||
# Consider Ruby on Rails.
|
||||
# With Rails you refer to a record's fields by their names.
|
||||
# We will (crudely) simulate that.
|
||||
class Record
|
||||
def initialize fields
|
||||
@fields = fields
|
||||
end
|
||||
|
||||
def method_missing m, *args
|
||||
meth_name = m.to_s
|
||||
if (meth_name.end_with?("=")) then
|
||||
@fields[meth_name.chop] = args[0]
|
||||
else
|
||||
@fields[meth_name]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
r = Record.new ({ 'fname' => 'Rick', 'lname' => 'Grimes', 'profession' => 'Police Officer' })
|
||||
puts r.profession
|
||||
r.profession = 'Zombie hunter'
|
||||
puts r.profession
|
||||
|
||||
# Ruby has const_missing as well, which works in a similar manner, except that it applies
|
||||
# to missing class constants
|
||||
|
||||
56
lab19/tree.rb
Normal file
56
lab19/tree.rb
Normal file
@@ -0,0 +1,56 @@
|
||||
class Tree
|
||||
attr_accessor :value, :left, :right
|
||||
def initialize(value, left=nil, right=nil)
|
||||
@value = value
|
||||
@left = left
|
||||
@right = right
|
||||
end
|
||||
|
||||
def each_node(&block)
|
||||
block.call(@value)
|
||||
@left.each_node(&block) if @left
|
||||
@right.each_node(&block) if @right
|
||||
end
|
||||
|
||||
def method_missing(method_name, *args)
|
||||
method_name = method_name.to_s
|
||||
path = method_name.scan(/(left|right)/).flatten
|
||||
current_node = self
|
||||
path.each do |direction|
|
||||
if direction == "left"
|
||||
current_node = current_node.left
|
||||
elsif direction == "right"
|
||||
current_node = current_node.right
|
||||
end
|
||||
return nil if current_node.nil?
|
||||
end
|
||||
return current_node.value
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
my_tree = Tree.new(42,
|
||||
Tree.new(3,
|
||||
Tree.new(1,
|
||||
Tree.new(7,
|
||||
Tree.new(22),
|
||||
Tree.new(123)),
|
||||
Tree.new(32))),
|
||||
Tree.new(99,
|
||||
Tree.new(81)))
|
||||
|
||||
my_tree.each_node do |v|
|
||||
puts v
|
||||
end
|
||||
|
||||
arr = []
|
||||
my_tree.each_node do |v|
|
||||
arr.push v
|
||||
end
|
||||
p arr
|
||||
|
||||
p "Getting nodes from tree"
|
||||
p my_tree.left_left
|
||||
p my_tree.right_left
|
||||
p my_tree.left_left_right
|
||||
p my_tree.left_left_left_right
|
||||
20
lab19/with-prob.rb
Normal file
20
lab19/with-prob.rb
Normal file
@@ -0,0 +1,20 @@
|
||||
def with_prob (prob)
|
||||
yield if (Random.rand < prob)
|
||||
end
|
||||
|
||||
with_prob 0.42 do
|
||||
puts "There is a 42% chance that this code will print"
|
||||
end
|
||||
|
||||
|
||||
|
||||
def foo x
|
||||
with_prob 0.5 do
|
||||
puts "Executing with_prob block"
|
||||
return 0
|
||||
end
|
||||
return x
|
||||
end
|
||||
|
||||
puts "Foo is #{foo 1}"
|
||||
|
||||
17
lab19/withProb.js
Normal file
17
lab19/withProb.js
Normal file
@@ -0,0 +1,17 @@
|
||||
function withProb(prob, f) {
|
||||
if (Math.random() < prob) {
|
||||
return f();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function foo(x) {
|
||||
withProb(0.5, function() {
|
||||
console.log("Execution withProb callback");
|
||||
return 0;
|
||||
});
|
||||
return x;
|
||||
}
|
||||
|
||||
console.log("Foo is " + foo(1));
|
||||
|
||||
Reference in New Issue
Block a user