Added seminar 2

This commit is contained in:
iko 2024-02-21 13:07:49 +03:00
parent dd8560938a
commit 14c9909fcb
Signed by untrusted user: iko
GPG Key ID: 82C257048D1026F2
8 changed files with 285 additions and 0 deletions

28
seminars/2/.gitignore vendored Normal file
View File

@ -0,0 +1,28 @@
dist
dist-*
cabal-dev
*.o
*.hi
*.hie
*.chi
*.chs.h
*.dyn_o
*.dyn_hi
.hpc
.hsenv
.cabal-sandbox/
cabal.sandbox.config
*.prof
*.aux
*.hp
*.eventlog
.stack-work/
cabal.project.local
cabal.project.local~
.HTF/
.ghc.environment.*
.DS_Store
.idea/
*.dump-hi
*.lock
*.iml

138
seminars/2/README.md Normal file
View File

@ -0,0 +1,138 @@
# Mathematical expression interpreter
Your task is to:
1. Write monadic parsing combinators
2. Define a structure to represent a parsed mathematical expression in Haskell
3. Write a parser, which parses a string into the structure
4. Write an interpreter for the structure, which performs the calculation and returns the result.
**NOTE: the parser and interpreter should be independent.**
You will have to handle division by zero errors.
## The basic expression
### Formal Definition
Note that the expressions can be nested arbitrarily deeply.
Also note, that a number can only be an integer.
The `/` operator is integer division truncated towards negative infinity (`div` in Haskell).
```bnf
<expression> ::= <number> | <operation>
<number> ::= "-" <digits> | <digits>
<digits> ::= <digit> | <digit> <digits>
<digit> ::= "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"
<operation> ::= "(" <spaces> <expression> <spaces> <operator> <spaces> <expression> <spaces> ")"
<operator> ::= "+" | "-" | "*" | "/"
<spaces> ::= "" | " " <spaces>
```
### Examples
These all represent valid expressions:
- `((1 + 2) * 3)`
- `1`
- `(((1 + 2) *3) +(1 + 1) )`
### Minimal required combinators
This combinator should read and return exactly one character from the input.
If there are no characters to read from the input, then it should fail.
```haskell
anyToken :: Parser Char
```
This combinator should read and return exactly one character from the input.
If the character does not match the given character, then it should fail.
```haskell
token :: Char -> Parser ()
```
This combinator should read a string of charracters. If the string does not match the given string then fail.
```haskell
tokens :: String -> Parser ()
```
This character reads any number of consecutive characters, which stisfy the predicate. So this can never fail.
```haskell
tokensWhile :: (Char -> Bool) -> Parser String
```
This character reads any number (but at least one) of consecutive characters, which stisfy the predicate. This can only fail if the first character does not satisfy the predicate.
```haskell
tokensWhile1 :: (Char -> Bool) -> Parser String
```
### Simple example
A sample parser might look something like this
```haskell
number :: Parser Int
number = do
sign <- signParser
digits <- digitsParser
return (read (sign <> digits))
```
### Control
The proposed approach of handling control is using the `Alternative` typeclass.
The instance for `[]` already provides the following behavior:
`empty` represents a failure (no values).
`<|>` represents parsing alternative. For example an expression can be either an operation or a number.
## Complex expressions
This is a slightly more complex version of expressions -- it includes conditional expressions
This represents the new parts:
```bnf
<expression> ::= <number> | <operation> | <conditional>
<conditional> ::= "if " <spaces> <bool_expression> <spaces> " then " <spaces> <expression> <spaces> " else " <spaces> <expression>
<bool_expression> ::= "(" <spaces> <expression> <spaces> <bool_operator> <spaces> <expression> <spaces> ")"
<bool_operator> = "=" | "<" | ">" | "<=" | ">="
```
### Examples
These all represent valid expressions:
- `if ( 1 = 2 ) then (2 + 4) else 2`
- `(((1 + 2) *3) +( if ( 1 = 2 ) then (2 + 4) else 2 + 1))`
## Artifacts
You should export the `calculate` function, which should parse the given string and return the computed result. If any of the steps involved fail (couldn't parse the string or there was a divide by zero error), you should return `Nothing`.
```haskell
calculate :: String -> Maybe Int
```
## Libraries you can use
You can use these two libraries:
- `base`
- `mtl`

19
seminars/2/package.yaml Normal file
View File

@ -0,0 +1,19 @@
name: task
version: 0.1.0.0
license: Unlicense
dependencies:
- base >= 4.7 && < 5
- mtl
library:
source-dirs: src
tests:
task-tests:
source-dirs: test
main: Main.hs
dependencies:
- tasty
- task
- tasty-hunit

12
seminars/2/src/Task.hs Normal file
View File

@ -0,0 +1,12 @@
module Task
( calculate,
)
where
import Control.Applicative
import Control.Monad.State.Lazy
import Data.Char
import Data.Maybe
calculate :: String -> Maybe Int
calculate = error "TODO: calculate"

7
seminars/2/stack.yaml Normal file
View File

@ -0,0 +1,7 @@
resolver: lts-22.6
packages:
- .
ghc-options:
"$locals": -ddump-to-file -ddump-hi -fwarn-unused-binds -fwarn-unused-imports -Wall -Wincomplete-uni-patterns -Wincomplete-record-updates -Werror=missing-home-modules -Wmissing-home-modules -Widentities -Wredundant-constraints -Wmissing-export-lists

44
seminars/2/task.cabal Normal file
View File

@ -0,0 +1,44 @@
cabal-version: 2.2
-- This file has been generated from package.yaml by hpack version 0.36.0.
--
-- see: https://github.com/sol/hpack
--
-- hash: 20a39a4ec02d6e75694b336bd8631bf5d630fcaacb76de84c7263c8abe1c45b3
name: task
version: 0.1.0.0
license: Unlicense
build-type: Simple
library
exposed-modules:
Task
other-modules:
Paths_task
autogen-modules:
Paths_task
hs-source-dirs:
src
build-depends:
base >=4.7 && <5
, mtl
default-language: Haskell2010
test-suite task-tests
type: exitcode-stdio-1.0
main-is: Main.hs
other-modules:
Tests
Paths_task
autogen-modules:
Paths_task
hs-source-dirs:
test
build-depends:
base >=4.7 && <5
, mtl
, task
, tasty
, tasty-hunit
default-language: Haskell2010

10
seminars/2/test/Main.hs Normal file
View File

@ -0,0 +1,10 @@
module Main
( main,
)
where
import Test.Tasty
import Tests
main :: IO ()
main = defaultMain tests

27
seminars/2/test/Tests.hs Normal file
View File

@ -0,0 +1,27 @@
module Tests
( tests,
)
where
import Task
import Test.Tasty
import Test.Tasty.HUnit
tests :: TestTree
tests = testGroup "Tests"
[ testCase "Basic expression" $ do
calculate "((1 + 2) * 3)" @=? Just 9
calculate "1" @=? Just 1
calculate "(((1 + 2) *3) +(1 + 1) )" @=? Just 11
calculate "(((1 + 2) *3) +(1 + 1) " @=? Nothing
calculate "1 + " @=? Nothing
calculate "(1 / (8 - 8))" @=? Nothing
calculate "(4 / 2)" @=? Just 2
, testCase "Complex expression" $ do
calculate "if ( 1 = 2 ) then (2 + 4) else 2" @=? Just 2
calculate "(((1 + 2) *3) +( if ( 1 = 2 ) then (2 + 4) else 2 + 1) )" @=? Just 12
calculate "(((1 + 2) *3) +( if 1 = 2 ) then (2 + 4) else 2 + 1) )" @=? Nothing
calculate "(((1 + 2) *3) +( if ( 1 = 2 ) then (2 + 4) else 2 + 1)" @=? Nothing
calculate "(((1 + 2) *3) +( f ( 1 = 2 ) then (2 + 4) else 2 + 1) )" @=? Nothing
calculate "(((1 + 2) *3) +( f ( 1 = 2 ) then (2 + 4) + 1) )" @=? Nothing
]