diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..6e2af67a588d0444df6806e258db0fabffc1c81a
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,400 @@
+
+# Created by https://www.toptal.com/developers/gitignore/api/python,linux,windows,macos,pycharm,emacs,visualstudiocode,vim
+# Edit at https://www.toptal.com/developers/gitignore?templates=python,linux,windows,macos,pycharm,emacs,visualstudiocode,vim
+
+### Emacs ###
+# -*- mode: gitignore; -*-
+*~
+\#*\#
+/.emacs.desktop
+/.emacs.desktop.lock
+*.elc
+auto-save-list
+tramp
+.\#*
+
+# Org-mode
+.org-id-locations
+*_archive
+
+# flymake-mode
+*_flymake.*
+
+# eshell files
+/eshell/history
+/eshell/lastdir
+
+# elpa packages
+/elpa/
+
+# reftex files
+*.rel
+
+# AUCTeX auto folder
+/auto/
+
+# cask packages
+.cask/
+dist/
+
+# Flycheck
+flycheck_*.el
+
+# server auth directory
+/server/
+
+# projectiles files
+.projectile
+
+# directory configuration
+.dir-locals.el
+
+# network security
+/network-security.data
+
+
+### Linux ###
+
+# temporary files which can be created if a process still has a handle open of a deleted file
+.fuse_hidden*
+
+# KDE directory preferences
+.directory
+
+# Linux trash folder which might appear on any partition or disk
+.Trash-*
+
+# .nfs files are created when an open file is removed but is still being accessed
+.nfs*
+
+### macOS ###
+# General
+.DS_Store
+.AppleDouble
+.LSOverride
+
+# Icon must end with two \r
+Icon
+
+
+# Thumbnails
+._*
+
+# Files that might appear in the root of a volume
+.DocumentRevisions-V100
+.fseventsd
+.Spotlight-V100
+.TemporaryItems
+.Trashes
+.VolumeIcon.icns
+.com.apple.timemachine.donotpresent
+
+# Directories potentially created on remote AFP share
+.AppleDB
+.AppleDesktop
+Network Trash Folder
+Temporary Items
+.apdisk
+
+### PyCharm ###
+# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
+# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
+
+# User-specific stuff
+.idea/**/workspace.xml
+.idea/**/tasks.xml
+.idea/**/usage.statistics.xml
+.idea/**/dictionaries
+.idea/**/shelf
+
+# Generated files
+.idea/**/contentModel.xml
+
+# Sensitive or high-churn files
+.idea/**/dataSources/
+.idea/**/dataSources.ids
+.idea/**/dataSources.local.xml
+.idea/**/sqlDataSources.xml
+.idea/**/dynamic.xml
+.idea/**/uiDesigner.xml
+.idea/**/dbnavigator.xml
+
+# Gradle
+.idea/**/gradle.xml
+.idea/**/libraries
+
+# Gradle and Maven with auto-import
+# When using Gradle or Maven with auto-import, you should exclude module files,
+# since they will be recreated, and may cause churn.  Uncomment if using
+# auto-import.
+# .idea/artifacts
+# .idea/compiler.xml
+# .idea/jarRepositories.xml
+# .idea/modules.xml
+# .idea/*.iml
+# .idea/modules
+# *.iml
+# *.ipr
+
+# CMake
+cmake-build-*/
+
+# Mongo Explorer plugin
+.idea/**/mongoSettings.xml
+
+# File-based project format
+*.iws
+
+# IntelliJ
+out/
+
+# mpeltonen/sbt-idea plugin
+.idea_modules/
+
+# JIRA plugin
+atlassian-ide-plugin.xml
+
+# Cursive Clojure plugin
+.idea/replstate.xml
+
+# Crashlytics plugin (for Android Studio and IntelliJ)
+com_crashlytics_export_strings.xml
+crashlytics.properties
+crashlytics-build.properties
+fabric.properties
+
+# Editor-based Rest Client
+.idea/httpRequests
+
+# Android studio 3.1+ serialized cache file
+.idea/caches/build_file_checksums.ser
+
+### PyCharm Patch ###
+# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
+
+# *.iml
+# modules.xml
+# .idea/misc.xml
+# *.ipr
+
+# Sonarlint plugin
+# https://plugins.jetbrains.com/plugin/7973-sonarlint
+.idea/**/sonarlint/
+
+# SonarQube Plugin
+# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin
+.idea/**/sonarIssues.xml
+
+# Markdown Navigator plugin
+# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced
+.idea/**/markdown-navigator.xml
+.idea/**/markdown-navigator-enh.xml
+.idea/**/markdown-navigator/
+
+# Cache file creation bug
+# See https://youtrack.jetbrains.com/issue/JBR-2257
+.idea/$CACHE_FILE$
+
+# CodeStream plugin
+# https://plugins.jetbrains.com/plugin/12206-codestream
+.idea/codestream.xml
+
+### Python ###
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+build/
+develop-eggs/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+pip-wheel-metadata/
+share/python-wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+MANIFEST
+
+# PyInstaller
+#  Usually these files are written by a python script from a template
+#  before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.nox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*.cover
+*.py,cover
+.hypothesis/
+.pytest_cache/
+pytestdebug.log
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+local_settings.py
+db.sqlite3
+db.sqlite3-journal
+
+# Flask stuff:
+instance/
+.webassets-cache
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+doc/_build/
+
+# PyBuilder
+target/
+
+# Jupyter Notebook
+.ipynb_checkpoints
+
+# IPython
+profile_default/
+ipython_config.py
+
+# pyenv
+.python-version
+
+# pipenv
+#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
+#   However, in case of collaboration, if having platform-specific dependencies or dependencies
+#   having no cross-platform support, pipenv may install dependencies that don't work, or not
+#   install all needed dependencies.
+#Pipfile.lock
+
+# PEP 582; used by e.g. github.com/David-OConnor/pyflow
+__pypackages__/
+
+# Celery stuff
+celerybeat-schedule
+celerybeat.pid
+
+# SageMath parsed files
+*.sage.py
+
+# Environments
+.env
+.venv
+env/
+venv/
+ENV/
+env.bak/
+venv.bak/
+pythonenv*
+
+# Spyder project settings
+.spyderproject
+.spyproject
+
+# Rope project settings
+.ropeproject
+
+# mkdocs documentation
+/site
+
+# mypy
+.mypy_cache/
+.dmypy.json
+dmypy.json
+
+# Pyre type checker
+.pyre/
+
+# pytype static type analyzer
+.pytype/
+
+# profiling data
+.prof
+
+### Vim ###
+# Swap
+[._]*.s[a-v][a-z]
+!*.svg  # comment out if you don't need vector files
+[._]*.sw[a-p]
+[._]s[a-rt-v][a-z]
+[._]ss[a-gi-z]
+[._]sw[a-p]
+
+# Session
+Session.vim
+Sessionx.vim
+
+# Temporary
+.netrwhist
+# Auto-generated tag files
+tags
+# Persistent undo
+[._]*.un~
+
+### VisualStudioCode ###
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
+*.code-workspace
+
+### VisualStudioCode Patch ###
+# Ignore all local history of files
+.history
+.ionide
+
+### Windows ###
+# Windows thumbnail cache files
+Thumbs.db
+Thumbs.db:encryptable
+ehthumbs.db
+ehthumbs_vista.db
+
+# Dump file
+*.stackdump
+
+# Folder config file
+[Dd]esktop.ini
+
+# Recycle Bin used on file shares
+$RECYCLE.BIN/
+
+# Windows Installer files
+*.cab
+*.msi
+*.msix
+*.msm
+*.msp
+
+# Windows shortcuts
+*.lnk
+
+# End of https://www.toptal.com/developers/gitignore/api/python,linux,windows,macos,pycharm,emacs,visualstudiocode,vim
diff --git a/add-upstream-remote.sh b/add-upstream-remote.sh
new file mode 100644
index 0000000000000000000000000000000000000000..2eb4a42dd2c19f1dacc6342841ad81351d98f5fa
--- /dev/null
+++ b/add-upstream-remote.sh
@@ -0,0 +1,26 @@
+#!/bin/bash
+
+# Detta script uppdaterar det lokala Git-arkivet med eventuella
+# ändringar som har gjorts "centralt", i upstream-arkivet, efter att
+# ditt eget arkiv skapades.  Om du behöver köra scriptet kommer vi att
+# säga till!
+
+# Exit script upon failure
+set -e
+
+# Is the upstream already set? If not, add it.
+# The upstream is the base repo that this archive was originally
+# forked from.
+git config remote.upstream.url || git remote add upstream git@gitlab.liu.se:tdde23-24/python-tdde24-base.git
+
+# Update from the "upstream" repo defined above
+git checkout main
+git fetch upstream
+
+# Don't start an editor to edit a merge message.
+# Too confusing and not so meaningful in this case.
+git pull upstream main --no-edit
+
+# Push updates to gitlab
+git push origin main
+
diff --git a/lab6/calc.py b/lab6/calc.py
new file mode 100644
index 0000000000000000000000000000000000000000..eb3375a70ffd795af9bfb00f883400528205ed9c
--- /dev/null
+++ b/lab6/calc.py
@@ -0,0 +1,285 @@
+# ----------------------------------------------------------------------------
+#  Primitive functions for the ConstCalc and Calc language constructs
+# ----------------------------------------------------------------------------
+
+
+# ----- PROGRAM -----
+
+
+def is_program(p):
+    return isinstance(p, list) and len(p) > 1 and p[0] == 'calc'
+
+
+def program_statements(p):
+    # The first item is 'calc', the rest are the statements
+    return p[1:]
+
+
+# ----- STATEMENTS -----
+
+
+def is_statements(p):
+    # A non-empty list of statements
+    return isinstance(p, list) and p and all(is_statement(s) for s in p)
+
+
+def first_statement(p):
+    return p[0]
+
+
+def rest_statements(p):
+    return p[1:]
+
+
+def empty_statements(p):
+    return not p
+
+
+# ----- STATEMENT -----
+
+
+def is_statement(s):
+    return (
+        is_assignment(s)
+        or is_repetition(s)
+        or is_selection(s)
+        or is_output(s)
+        or is_input(s)
+    )
+
+
+# ----- ASSIGNMENT -----
+
+
+def is_assignment(p):
+    return isinstance(p, list) and len(p) == 3 and p[0] == 'set'
+
+
+def assignment_variable(p):
+    return p[1]
+
+
+def assignment_expression(p):
+    return p[2]
+
+
+# ----- REPETITION -----
+
+
+def is_repetition(p):
+    return isinstance(p, list) and len(p) > 2 and p[0] == 'while'
+
+
+def repetition_condition(p):
+    return p[1]
+
+
+def repetition_statements(p):
+    return p[2:]
+
+
+# ----- SELECTION -----
+
+
+def is_selection(p):
+    return isinstance(p, list) and (3 <= len(p) <= 4) and p[0] == 'if'
+
+
+def selection_condition(p):
+    return p[1]
+
+
+def selection_true_branch(p):
+    return p[2]
+
+
+def selection_has_false_branch(p):
+    return len(p) == 4
+
+
+def selection_false_branch(p):
+    return p[3]
+
+
+# ----- INPUT -----
+
+
+def is_input(p):
+    return isinstance(p, list) and len(p) == 2 and p[0] == 'read'
+
+
+def input_variable(p):
+    return p[1]
+
+
+# ----- OUTPUT -----
+
+
+def is_output(p):
+    return isinstance(p, list) and len(p) == 2 and p[0] == 'print'
+
+
+def output_expression(p):
+    return p[1]
+
+
+# ----- EXPRESSION -----
+
+# No functions for expressions in general. Instead, see the differenct
+# types of expressions: constants, variables and binary expressions.
+
+
+# ----- BINARYEXPR -----
+
+
+def is_binaryexpr(p):
+    return isinstance(p, list) and len(p) == 3 and is_binaryoper(p[1])
+
+
+def binaryexpr_operator(p):
+    return p[1]
+
+
+def binaryexpr_left(p):
+    return p[0]
+
+
+def binaryexpr_right(p):
+    return p[2]
+
+
+# ----- CONDITION -----
+
+
+def is_condition(p):
+    return isinstance(p, list) and len(p) == 3 and is_condoper(p[1])
+
+
+def condition_operator(p):
+    return p[1]
+
+
+def condition_left(p):
+    return p[0]
+
+
+def condition_right(p):
+    return p[2]
+
+
+# ----- BINARYOPER -----
+
+
+def is_binaryoper(p):
+    return p in ['+', '-', '*', '/']
+
+
+# ----- CONDOPER -----
+
+
+def is_condoper(p):
+    return p in ['<', '>', '=']
+
+
+# ----- VARIABLE -----
+
+
+def is_variable(p):
+    return isinstance(p, str) and p != ""
+
+# ----- CONSTANT -----
+
+
+def is_constant(p):
+    return isinstance(p, int) or isinstance(p, float)
+
+
+# ----------------------------------------------------------------------------
+#  Grammar for the *complete* Calc language
+# ----------------------------------------------------------------------------
+
+"""
+
+    (* För att vi inte själva ska råka läsa fel och blanda ihop EBNF-komma
+    och det ',' som ingår i vårt språk skapar vi en icke-terminal för detta... *)
+    COMMA = ',' ;
+
+    (* Ett program består av en följd av satser.  Eftersom ordet calc ska stå inom 
+    apostrofer behöver vi lägga detta inom citattecken i grammatiken. Jämför med att 
+    hakparenteserna ska vara utan apostrofer i vårt språk, men *har* en nivå av 
+    apostrofer i grammatiken. *)
+    PROGRAM = '[', "'calc'", COMMA, STATEMENTS, ']' ;
+
+    (* STATEMENTS är ett ensamt STATEMENT, eller ett STATEMENT följt av komma 
+           och STATEMENTS (som i sin tur är 1 STATEMENT som möjligen följs av flera,
+        och så vidare).  *)
+    STATEMENTS = 
+        STATEMENT
+      | STATEMENT, COMMA, STATEMENTS ;
+    
+    (* En sats kan vara en tilldelning, en upprepning, ett val,
+       en inmatning eller en utmatning. *)
+    STATEMENT =
+        ASSIGNMENT
+      | REPETITION
+      | SELECTION
+      | INPUT
+      | OUTPUT ;
+
+    (* En tilldelning består av en variabel och ett uttryck vars värde ska beräknas
+       för att sedan kopplas till det givna variabelnamnet. *)
+    ASSIGNMENT = '[', "'set'", COMMA, VARIABLE, COMMA, EXPRESSION, ']' ;
+
+    (* En upprepning består av ett villkorsuttryck och en följd av satser,
+       vilka upprepas så länge villkorsuttrycket är sant.  *)
+    REPETITION = '[', "'while'", COMMA, CONDITION, COMMA, STATEMENTS, ']' ;
+
+    (* Ett val består av ett villkorsuttryck följt av en eller två satser.
+       Den första satsen utförs om villkorsuttrycket är sant,
+       den andra (om den finns) om villkorsuttrycket är falskt.
+       Notera att [ ... ] betyder att det som står inom hakparenteserna
+       får utelämnas ("optional").  *)
+    SELECTION = '[', "'if'", COMMA, CONDITION, COMMA, STATEMENT, [COMMA, STATEMENT], ']'
+
+    (* En inmatningssats anger namnet på en variabel som ska få ett
+       numeriskt värde av användaren. *)
+    INPUT = '[', "'read'", COMMA, VARIABLE, ']' ;
+
+    (* En utmatningssats anger ett uttryck vars värde ska skrivas ut. *)
+    OUTPUT = '[', "'print'", COMMA, EXPRESSION, ']' ;
+
+    (* Ett matematiskt uttryck kan vara en konstant, en variabel eller
+       ett binärt uttryck. *)
+    EXPRESSION =
+        CONSTANT
+      | VARIABLE
+      | BINARYEXPR ;
+
+    (* Ett binärt uttryck består av två uttryck med en matematisk operator i mitten. *)
+    BINARYEXPR = '[', EXPRESSION, COMMA, BINARYOPER, COMMA, EXPRESSION, ']' ;
+
+    (* Ett villkor består av två uttryck med en villkorsoperator i mitten. *)
+    CONDITION = '[', EXPRESSION, COMMA, CONDOPER, COMMA, EXPRESSION, ']' ;
+
+    (* En binäroperator symboliserar ett av de fyra grundläggande räknesätten.
+       Eftersom man i språket måste skriva detta med citattecken som i
+           [10, "+", 20]
+       måste vi här ha med *dubbla* citattecken.  Om vi bara skrev '+'
+       skulle uttrycket vara
+           [10, +, 20]
+       vilket inte kan tolkas i Python.
+    *)
+    BINARYOPER = "'+'" | "'-'" | "'*'" | "'/'" ;
+
+    (* En villkorsoperator är större än, mindre än eller lika med. *)
+    CONDOPER = "'<'" | "'>'" | "'='" ;
+
+    (* En variabel är en sträng definierad som i Python -- strängen anger
+       namnet på variabeln.  Text mellan två frågetecken anger att något
+       är definierat utanför EBNF -- vi går alltså inte så långt som att
+       vi definierar exakt hur en sträng ser ut. *)
+    VARIABLE = ? a Python string ? ;
+
+    (* En konstant är ett tal i Python. *)
+    CONSTANT = ? a Python number ? ;
+"""
diff --git a/lab6/labb6.py b/lab6/labb6.py
new file mode 100644
index 0000000000000000000000000000000000000000..fb9e0056c8642a73b0043f120bce6515086ba5db
--- /dev/null
+++ b/lab6/labb6.py
@@ -0,0 +1,187 @@
+from calc import *
+import copy
+
+        
+######## ex1: c = ['calc', ['if', [4, '>', 8], ['print', 3], ['print', 7]]]
+        
+  ###   körexempel för felaktiga konstruktioner ###   
+######## ex1: c = [['print', 3], 'calc', ['print', 7]]
+######## ex2: c = ['calc', [[4, '>', 8], ['print', 3],'if',  ['print', 7]]]
+######## ex3: c = []
+######## ex4: c = ['calc', ['if', [4, '>=', 8], ['print', 3], ['print', 7]]]
+
+  
+### ex1: calc1 = ['calc', ['set', 'a', 5], ['print', 'a']]
+  #       new_table = exec_program(calc1)  -->  a = 9
+  #       my_table = {'a': 6}
+  #       new_table = exec_program(calc1, my_table) -->  a = 9
+  #       my_table    --> {'a': 6}
+  
+### ex2: calc2 = ['calc', ['set', 'x', 10], ['set', 'y', 10],['set', 'z', ['x', '*', 'y']],['set', 't', ['z', '-', 'x']],['print', 't']]
+  #       new_table = exec_program(calc2) --> t = 90
+  #       new_table --> {'x': 10, 'y': 10, 'z': 100, 't': 90}
+  
+### ex3: calc3 = ['calc', ['read', 'p1'],['set', 'p3', 10],['set', 'result', [['p1', '*', 20], '-', ['p3', '*', 20]]],['print', 'result']]
+  #       new_table = exec_program(calc3)
+  #       Enter value for p1: 10
+  #       result = 0
+
+### ex4: calc4 = ['calc', ['read', 'n'],['set', 'sum', 7],['while', ['n', '>', 5],['set', 'sum', ['sum', '+', 'n']],['set', 'n', ['n', '-', 2]]],['set', 'z', ['sum', '*', 'n']],['print', 'z']]
+  #       new_table = exec_program(calc4)
+  #       Enter value for n: 9
+  #       z = 115
+
+######## ######## ######## ######## ######## ######## ######## ######## ######## ######## ######## ######## ######## ######## ######## ######## ######## ######## ######## ######## ######## ######## ######## ########
+def exec_program(p, my_table={}):
+    ''' En funktion som tar emot ett ett Calc-program och följer olika "regler" för ConstCalc för att göra olika saker '''
+
+    #Testar om det är ett program
+    if is_program(p) == True:
+        return exec_statements(program_statements(p),my_table)
+    
+def exec_statements(p,my_table):
+    ''' En funktion som tar emot ett statements och delar upp de i minre delarolika saker '''
+    
+    #Testar om tabelen är töm, och sen skickar vidare en statement i taget till en ny funktion
+    if not empty_statements(p):
+        my_table = exec_statement(first_statement(p), my_table)
+        return exec_statements(rest_statements(p), my_table)
+    else:
+        return my_table
+     
+def exec_statement(p, my_table):
+    ''' En funktion som tar emot ett statement och skickar det vidare baserat på vad den är något'''
+
+
+    #Testar om det är en statement som skriver ut
+    if is_output(p):
+        return exec_output(p,my_table)
+    
+    #Testar om det är en statement som tar emot en imantning
+    elif is_input(p):
+        return exec_input(p,my_table)
+    
+    #Testar om det är en statement som har en villkorsuttryck
+    elif is_selection(p):
+        return exec_selection(p, my_table)
+
+    #Testar om det är en tilldelning statement 
+    elif is_assignment(p):
+        return exec_assignment(p,my_table)
+
+    #Testar om det är en repetition statement        
+    elif is_repetition(p):
+        return exec_repetition(p,my_table)
+
+    
+def exec_output(p,my_table):
+    ''' En funktion som tar emot ett statement och skriver ut olika saker'''
+
+    #Testar om output uttryck som sḱa skrivas ut finns i my_table, och i såfall skriv ut det
+    #Annars skriv ut output uttryck direkt
+    if output_expression(p) in my_table:
+        print(output_expression(p),'=', my_table[output_expression(p)])
+        return my_table
+        
+    else:
+        print(output_expression(p))
+        return my_table
+
+
+def exec_selection(p,my_table):
+    '''  En funktion som tar emot ett statement som består av ett villkorsuttryck följt av en eller två satser. Den första satsen utförs om villkorsuttrycket är sant, den andra (om den finns) om villkorsuttrycket är falskt.'''
+
+    #Testar om urvalsvillkor är möjligt
+    if is_condition(selection_condition(p)):
+
+        #Jämför olika fall med villkorsuttryck med satserna 
+        #Om den givna vilkoret är sant så utförd den första satsen villkorsuttrycket som sant
+        # Annars så utförs den första satsen villkorsuttrycket som falskt
+        if eval_condition(selection_condition(p), my_table):
+            return exec_statement(selection_true_branch(p),my_table)
+        elif selection_has_false_branch(p) :
+            return exec_statement(selection_false_branch(p),my_table)
+        return my_table
+        
+def exec_assignment(p,my_table):
+    ''' En funktion som tar emot ett statement som består av en variabel och ett uttryck vars värde ska beräknas för att sedan kopplas till det givna variabelnamnet'''
+
+    #Skapar en kopia av my_table och sen gör ändringar där
+    #I kopian tillde värdet av uttrycket tiĺl variabel   
+    my_copy = dict.copy(my_table)
+    my_copy[assignment_variable(p)] = eval_expression(assignment_expression(p), my_table)
+    return my_copy
+                
+    
+def exec_input(p,my_table):
+    ''' En funktion som tar emot ett statement som är en inmatningssats anger namnet på en variabel som ska få ett numeriskt värde av användaren.'''
+
+    #Skapar en kopia av my_table och sen gör ändringar där
+    #I kopian ska  en variabel få ett numeriskt värde av användaren
+    my_copy = dict.copy(my_table)
+    x = int(input('Enter value for ' + input_variable(p)+': ' ))
+    my_copy[input_variable(p)] = x
+    return my_copy
+
+def exec_repetition(p,my_table):
+    ''' En funktion som tar emot ett statement består av ett villkorsuttryck och en följd av satser, vilka upprepas så länge villkorsuttrycket är sant.'''
+
+    #Så länge sä villkorsuttryck är sant kommer följd satser att upprepas
+    while eval_condition(repetition_condition(p),my_table):
+        my_table = exec_statements(repetition_statements(p), my_table)
+    return my_table
+    
+def eval_expression(p,my_table):
+    ''' En funktion som tar emot ett siffra, bosktav eller lista'''
+
+    #Testar om det är en siffra
+    if is_constant(p):
+        return p
+    
+    #Testa om det är en bokstav
+    elif is_variable(p):
+        return my_table[p]
+    
+    #Testar om det finns ett binärt uttryck
+    elif is_binaryexpr(p):
+        return eval_binary(p,my_table)
+
+
+def eval_condition(p,my_table):
+    ''' En funktion som tar emot olika satser och jämför med vrandra'''
+
+    #Testar om vänstra satsen är större än högra
+    if condition_operator(p) == '>':
+        return eval_expression(condition_left(p),my_table) > eval_expression(condition_right(p),my_table)
+    
+    #Testar om vänstra satsen är mindre än högra
+    elif condition_operator(p) == '<':
+        return eval_expression(condition_left(p),my_table) <  eval_expression(condition_right(p),my_table)
+    
+    #Testar om vänstra satsen är lika med högra
+    elif condition_operator(p) == '=':
+        return eval_expression(condition_left(p),my_table) == eval_expression(condition_right(p),my_table)
+
+
+def eval_binary(p,my_table):
+    ''' En miniräknare'''
+
+    left = eval_expression(binaryexpr_left(p), my_table)
+    right = eval_expression(binaryexpr_right(p), my_table)
+
+    #Testar om binaryexpr operator är plus och isåfall addera vänster med höger
+    if binaryexpr_operator(p) == '+':
+        return left + right
+
+    #Testar om binaryexpr operator är minus och isåfall subtrahera vänster från höger
+    elif binaryexpr_operator(p) == '-':
+        return left - right
+
+    #Testar om binaryexpr operator är gånger och isåfall multiplicera vänster med höger
+    elif binaryexpr_operator(p) == '*':
+        return left * right
+
+    #Testar om binaryexpr operator är dela och isåfall dividera vänster med höger
+    elif binaryexpr_operator(p) == '/':
+        return left / right
+         
diff --git a/lab6/test_6.py b/lab6/test_6.py
new file mode 100644
index 0000000000000000000000000000000000000000..6a9eb314911ea539466a2913b6584713edf89e66
--- /dev/null
+++ b/lab6/test_6.py
@@ -0,0 +1,670 @@
+#!/usr/bin/env python3
+"""
+A test unit for the calc interpreter.
+
+Note:
+If you have downloaded the scripts from the website it might not
+have the access right. To solve this run:
+$ chmod +x <path_to_test_6.py>
+
+Usage:
+$ ./test_6.py <path_to_lab>
+or
+$ ./test_6.py --test a <path_to_lab>
+or
+$ ./test_6.py --test A <path_to_lab>
+to test lab 6B
+$ ./test_6.py --test alt <path_to_lab>
+or
+$ ./test_6.py --test ALT <path_to_lab>
+to test with alternative_calc.py, remember to import it instead of calc in the source
+
+Initial version by Erik Hansson <erik.b.hansson@liu.se>
+
+Changelog:
+ * 31/8-2016: Updated the printed traceback in case that the given file
+   can not be imported.
+"""
+
+from argparse import ArgumentParser
+from importlib.machinery import SourceFileLoader
+from traceback import format_exc
+from unittest import TestCase, defaultTestLoader, TextTestRunner
+import sys
+
+
+def print_stdout(val):
+    """
+    Prints val to standard stdout
+    """
+    current_out = sys.stdout
+    sys.stdout = sys.__stdout__
+    print(val)
+    sys.stdout = current_out
+
+
+class InputGenerator:
+    """
+    An input class for creating inputs instead fo stdin
+    """
+
+    @staticmethod
+    def generator(values):
+        """
+        Creates a generator that yields all the values as strings
+        """
+        for elem in values:
+            yield (str(elem))
+
+    def __init__(self, values=None):
+        if values is not None:
+            self._generator = InputGenerator.generator(values)
+        else:
+            self._generator = None
+
+    def readline(self):
+        """
+        Reads one item from the generator or empty string if there is no
+        generator.
+        """
+        if self._generator is not None:
+            return self._generator.__next__()
+        else:
+            return ""
+
+    def setInputFeed(self, values):
+        """
+        Sets the next inputs to be those of values.
+        Note that this flushes the input stream.
+        """
+        self._generator = InputGenerator.generator(values)
+
+
+class OutputObject:
+    """
+    A simple output class for capturing any prints
+    """
+
+    def __init__(self):
+        self._output_queue = []
+        self._string_buffer = ""
+
+    def write(self, string):
+        """
+        Writes a string to the output buffer
+        """
+        while "\n" in string:
+            self._string_buffer += string[: string.index("\n") + 1]
+            self._output_queue.append(self._string_buffer)
+            self._string_buffer = ""
+            string = string[string.index("\n") + 1 :]
+        self._string_buffer += string
+
+    def readline(self):
+        """
+        Reads a line from the output queue or everything that is in the
+        temporary string buffer if no new line has been written.
+        """
+        if len(self._output_queue) > 0:
+            val = self._output_queue[0]
+            self._output_queue = self._output_queue[1:]
+        else:
+            val = self._string_buffer
+            self._string_buffer = ""
+        return val
+
+    def flush(self):
+        """
+        Sends the output string buffer to the output queue
+        """
+        self._output_queue.append(self._string_buffer)
+        self._string_buffer = ""
+
+
+class TestEvalProgram(TestCase):
+    """
+    A unit test for the eval program
+    """
+
+    _set_a_prog = ["calc", ["set", "a", 7]]
+    _print_and_set_a_prog = ["calc", ["print", "a"], ["set", "a", 0]]
+    _input_a_prog = ["calc", ["read", "a"]]
+    _print_a_prog = ["calc", ["print", "a"]]
+    _print_if_prog = ["calc", ["if", ["a", ">", "b"], ["print", "a"], ["print", "a"]]]
+    _read_and_print_a_prog = ["calc", ["read", "a"], ["print", "a"]]
+    _if_prog = [
+        "calc",
+        ["read", "x"],
+        ["set", "zero", 0],
+        ["set", "pos", 1],
+        ["set", "nonpos", -1],
+        ["if", ["x", "=", 0], ["print", "zero"]],
+        ["if", ["x", ">", 0], ["print", "pos"]],
+        ["if", ["x", "<", 0], ["print", "nonpos"]],
+    ]
+    _if_set_prog = [
+        "calc",
+        ["read", "x"],
+        ["if", ["x", ">", 0], ["set", "a", 1], ["set", "a", -1]],
+        ["if", ["x", "=", 0], ["set", "a", 0]],
+    ]
+    _loop_prog = [
+        "calc",
+        ["read", "n"],
+        ["set", "sum", 0],
+        [
+            "while",
+            ["n", ">", 0],
+            ["set", "sum", ["sum", "+", "n"]],
+            ["set", "n", ["n", "-", 1]],
+        ],
+        ["print", "sum"],
+    ]
+    _loop_with_binexpr_prog = [
+        "calc",
+        ["read", "n"],
+        ["set", "sum", 0],
+        [
+            "while",
+            [["n", "-", 1], ">", 0],
+            ["set", "sum", ["sum", "+", "n"]],
+            ["set", "n", ["n", "-", 1]],
+        ],
+        ["print", "sum"],
+    ]
+
+    def tearDown(self):
+        """
+        Restors the system input and output streams after the tests
+        has been conducted
+        """
+        sys.stdin = sys.__stdin__
+        sys.stdout = sys.__stdout__
+
+    def setUp(self):
+        """
+        Creates some sample programs and rewrites the system
+        output and input to buffer objects
+        """
+        self._input = InputGenerator()
+        sys.stdin = self._input
+        self._output = OutputObject()
+        sys.stdout = self._output
+
+    def testReturnValue(self):
+        """
+        Tests so that the returned variable table are correct
+        """
+        new_vars = lab6.exec_program(self._set_a_prog)
+        self.assertEqual(new_vars, {"a": 7})
+        self._input.setInputFeed([10])
+        new_vars = lab6.exec_program(self._input_a_prog)
+        self.assertEqual(new_vars, {"a": 10})
+
+    def testDestructiveness(self):
+        """
+        Tests so that the function is not destructive
+        """
+        my_vars = {"a": 5}
+
+        # Should deepcopy if necessary
+        new_vars = lab6.exec_program(self._set_a_prog, my_vars)
+        self.assertEqual(my_vars, {"a": 5})
+        self.assertEqual(new_vars, {"a": 7})
+        self.assertIsNot(my_vars, new_vars)
+
+        # Should deepcopy if necessary
+        self._input.setInputFeed([10])
+        new_vars = lab6.exec_program(self._input_a_prog, my_vars)
+        self.assertEqual(my_vars, {"a": 5})
+        self.assertEqual(new_vars, {"a": 10})
+        self.assertIsNot(my_vars, new_vars)
+
+        # Should deepcopy if necessary
+        new_vars = lab6.exec_program(self._print_and_set_a_prog, my_vars)
+        self.assertEqual(my_vars, {"a": 5})
+        self.assertEqual(new_vars, {"a": 0})
+        self.assertIsNot(my_vars, new_vars)
+
+        # Shouldn't deepcopy if this isn't necessary
+        new_vars = lab6.exec_program(self._print_a_prog, my_vars)
+        self.assertIs(my_vars, new_vars)
+
+        # Shouldn't deepcopy if this isn't necessary
+        my_vars = {"a": 5, "b": 10}
+        new_vars = lab6.exec_program(self._print_if_prog, my_vars)
+        self.assertIs(my_vars, new_vars)
+
+    def testIO(self):
+        """
+        Tests simple input output functionality
+        """
+        self._input.setInputFeed([4])
+        lab6.exec_program(self._read_and_print_a_prog)
+        self.assertEqual(self._output.readline(), "Enter value for a: ")
+        self.assertEqual(self._output.readline(), "a = 4\n")
+
+    def testIf(self):
+        """
+        Tests the use of if-statements in calc
+        """
+        self._input.setInputFeed([-3, -1, 0, 1, 9])
+        new_vars = lab6.exec_program(self._if_prog)
+        self.assertEqual(new_vars, {"zero": 0, "pos": 1, "nonpos": -1, "x": -3})
+        self.assertEqual(self._output.readline(), "Enter value for x: ")
+        self.assertEqual(self._output.readline(), "nonpos = -1\n")
+        new_vars = lab6.exec_program(self._if_prog)
+        self.assertEqual(new_vars, {"zero": 0, "pos": 1, "nonpos": -1, "x": -1})
+        self.assertEqual(self._output.readline(), "Enter value for x: ")
+        self.assertEqual(self._output.readline(), "nonpos = -1\n")
+        new_vars = lab6.exec_program(self._if_prog)
+        self.assertEqual(new_vars, {"zero": 0, "pos": 1, "nonpos": -1, "x": 0})
+        self.assertEqual(self._output.readline(), "Enter value for x: ")
+        self.assertEqual(self._output.readline(), "zero = 0\n")
+        new_vars = lab6.exec_program(self._if_prog)
+        self.assertEqual(new_vars, {"zero": 0, "pos": 1, "nonpos": -1, "x": 1})
+        self.assertEqual(self._output.readline(), "Enter value for x: ")
+        self.assertEqual(self._output.readline(), "pos = 1\n")
+        new_vars = lab6.exec_program(self._if_prog)
+        self.assertEqual(new_vars, {"zero": 0, "pos": 1, "nonpos": -1, "x": 9})
+        self.assertEqual(self._output.readline(), "Enter value for x: ")
+        self.assertEqual(self._output.readline(), "pos = 1\n")
+
+        # Test if_set
+        self._input.setInputFeed([-3, 0, 1])
+        new_vars = lab6.exec_program(self._if_set_prog)
+        self.assertEqual(new_vars, {"x": -3, "a": -1})
+        self.assertEqual(self._output.readline(), "Enter value for x: ")
+
+        new_vars = lab6.exec_program(self._if_set_prog)
+        self.assertEqual(new_vars, {"x": 0, "a": 0})
+        self.assertEqual(self._output.readline(), "Enter value for x: ")
+
+        new_vars = lab6.exec_program(self._if_set_prog)
+        self.assertEqual(new_vars, {"x": 1, "a": 1})
+        self.assertEqual(self._output.readline(), "Enter value for x: ")
+
+    def testLoop(self):
+        """
+        Tests the use of loops in calc
+        """
+        self._input.setInputFeed([4, 1, 0, -1, 2])
+        new_vars = lab6.exec_program(self._loop_prog)
+        self.assertEqual(new_vars, {"n": 0, "sum": 10})
+        self.assertEqual(self._output.readline(), "Enter value for n: ")
+        self.assertEqual(self._output.readline(), "sum = 10\n")
+        new_vars = lab6.exec_program(self._loop_prog)
+        self.assertEqual(new_vars, {"n": 0, "sum": 1})
+        self.assertEqual(self._output.readline(), "Enter value for n: ")
+        self.assertEqual(self._output.readline(), "sum = 1\n")
+        new_vars = lab6.exec_program(self._loop_prog)
+        self.assertEqual(new_vars, {"n": 0, "sum": 0})
+        self.assertEqual(self._output.readline(), "Enter value for n: ")
+        self.assertEqual(self._output.readline(), "sum = 0\n")
+        new_vars = lab6.exec_program(self._loop_prog)
+        self.assertEqual(new_vars, {"n": -1, "sum": 0})
+        self.assertEqual(self._output.readline(), "Enter value for n: ")
+        self.assertEqual(self._output.readline(), "sum = 0\n")
+
+        # Test once with an binary expression in the condition
+        new_vars = lab6.exec_program(self._loop_with_binexpr_prog)
+        self.assertEqual(new_vars, {"n": 1, "sum": 2})
+        self.assertEqual(self._output.readline(), "Enter value for n: ")
+        self.assertEqual(self._output.readline(), "sum = 2\n")
+
+
+class TestAltEvalProgram(TestCase):
+    """
+    A unit test for the eval program, with the alternative Calc structure
+    """
+
+    _set_a_prog = {
+        "type": "calc",
+        "do": {"statements": [{"type": "set", "var": "a", "expr": 7}]},
+    }
+    _print_and_set_a_prog = {
+        "type": "calc",
+        "do": {
+            "statements": [
+                {"type": "print", "expr": "a"},
+                {"type": "set", "var": "a", "expr": 0},
+            ]
+        },
+    }
+    _input_a_prog = {
+        "type": "calc",
+        "do": {"statements": [{"type": "read", "var": "a"}]},
+    }
+    _print_a_prog = {
+        "type": "calc",
+        "do": {"statements": [{"type": "print", "expr": "a"}]},
+    }
+    _print_if_prog = {
+        "type": "calc",
+        "do": {
+            "statements": [
+                {
+                    "type": "if",
+                    "cond": {"left": "a", "op": ">", "right": "b"},
+                    "true": {"type": "print", "expr": "a"},
+                    "false": {"type": "print", "expr": "a"},
+                },
+            ]
+        },
+    }
+    _read_and_print_a_prog = {
+        "type": "calc",
+        "do": {
+            "statements": [
+                {"type": "read", "var": "a"},
+                {"type": "print", "expr": "a"},
+            ]
+        },
+    }
+    _if_prog = {
+        "type": "calc",
+        "do": {
+            "statements": [
+                {"type": "read", "var": "x"},
+                {"type": "set", "var": "zero", "expr": 0},
+                {"type": "set", "var": "pos", "expr": 1},
+                {"type": "set", "var": "nonpos", "expr": -1},
+                {
+                    "type": "if",
+                    "cond": {"left": "x", "op": "=", "right": 0},
+                    "true": {"type": "print", "expr": "zero"},
+                },
+                {
+                    "type": "if",
+                    "cond": {"left": "x", "op": ">", "right": 0},
+                    "true": {"type": "print", "expr": "pos"},
+                },
+                {
+                    "type": "if",
+                    "cond": {"left": "x", "op": "<", "right": 0},
+                    "true": {"type": "print", "expr": "nonpos"},
+                },
+            ]
+        },
+    }
+    _if_set_prog = {
+        "type": "calc",
+        "do": {
+            "statements": [
+                {"type": "read", "var": "x"},
+                {
+                    "type": "if",
+                    "cond": {"left": "x", "op": ">", "right": 0},
+                    "true": {"type": "set", "var": "a", "expr": 1},
+                    "false": {"type": "set", "var": "a", "expr": -1},
+                },
+                {
+                    "type": "if",
+                    "cond": {"left": "x", "op": "=", "right": 0},
+                    "true": {"type": "set", "var": "a", "expr": 0},
+                },
+            ]
+        },
+    }
+    _loop_with_binexpr_prog = {
+        "type": "calc",
+        "do": {
+            "statements": [
+                {"type": "read", "var": "n"},
+                {"type": "set", "var": "sum", "expr": 0},
+                {
+                    "type": "while",
+                    "cond": {
+                        "left": {"left": "n", "op": "-", "right": 1},
+                        "op": ">",
+                        "right": 0,
+                    },
+                    "do": {
+                        "statements": [
+                            {
+                                "type": "set",
+                                "var": "sum",
+                                "expr": {"left": "sum", "op": "+", "right": "n"},
+                            },
+                            {
+                                "type": "set",
+                                "var": "n",
+                                "expr": {"left": "n", "op": "-", "right": 1},
+                            },
+                        ],
+                    },
+                },
+                {"type": "print", "expr": "sum"},
+            ]
+        },
+    }
+    _loop_prog = {
+        "type": "calc",
+        "do": {
+            "statements": [
+                {"type": "read", "var": "n"},
+                {"type": "set", "var": "sum", "expr": 0},
+                {
+                    "type": "while",
+                    "cond": {"left": "n", "op": ">", "right": 0},
+                    "do": {
+                        "statements": [
+                            {
+                                "type": "set",
+                                "var": "sum",
+                                "expr": {"left": "sum", "op": "+", "right": "n"},
+                            },
+                            {
+                                "type": "set",
+                                "var": "n",
+                                "expr": {"left": "n", "op": "-", "right": 1},
+                            },
+                        ],
+                    },
+                },
+                {"type": "print", "expr": "sum"},
+            ]
+        },
+    }
+
+    def tearDown(self):
+        """
+        Restors the system input and output streams after the tests
+        has been conducted
+        """
+        sys.stdin = sys.__stdin__
+        sys.stdout = sys.__stdout__
+
+    def setUp(self):
+        """
+        Creates some sample programs and rewrites the system
+        output and input to buffer objects
+        """
+        self._input = InputGenerator()
+        sys.stdin = self._input
+        self._output = OutputObject()
+        sys.stdout = self._output
+
+    def testReturnValue(self):
+        """
+        Tests so that the returned variable table are correct
+        """
+        new_vars = lab6.exec_program(self._set_a_prog)
+        self.assertEqual(new_vars, {"a": 7})
+        self._input.setInputFeed([10])
+        new_vars = lab6.exec_program(self._input_a_prog)
+        self.assertEqual(new_vars, {"a": 10})
+
+    def testDestructiveness(self):
+        """
+        Tests so that the function is not destructive
+        """
+        my_vars = {"a": 5}
+
+        # Should deepcopy if necessary
+        new_vars = lab6.exec_program(self._set_a_prog, my_vars)
+        self.assertEqual(my_vars, {"a": 5})
+        self.assertEqual(new_vars, {"a": 7})
+        self.assertIsNot(my_vars, new_vars)
+
+        # Should deepcopy if necessary
+        self._input.setInputFeed([10])
+        new_vars = lab6.exec_program(self._input_a_prog, my_vars)
+        self.assertEqual(my_vars, {"a": 5})
+        self.assertEqual(new_vars, {"a": 10})
+        self.assertIsNot(my_vars, new_vars)
+
+        # Should deepcopy if necessary
+        new_vars = lab6.exec_program(self._print_and_set_a_prog, my_vars)
+        self.assertEqual(my_vars, {"a": 5})
+        self.assertEqual(new_vars, {"a": 0})
+        self.assertIsNot(my_vars, new_vars)
+
+        # Shouldn't deepcopy if this isn't necessary
+        new_vars = lab6.exec_program(self._print_a_prog, my_vars)
+        self.assertIs(my_vars, new_vars)
+
+        # Shouldn't deepcopy if this isn't necessary
+        my_vars = {"a": 5, "b": 10}
+        new_vars = lab6.exec_program(self._print_if_prog, my_vars)
+        self.assertIs(my_vars, new_vars)
+
+    def testIO(self):
+        """
+        Tests simple input output functionality
+        """
+        self._input.setInputFeed([4])
+        lab6.exec_program(self._read_and_print_a_prog)
+        self.assertEqual(self._output.readline(), "Enter value for a: ")
+        self.assertEqual(self._output.readline(), "a = 4\n")
+
+    def testIf(self):
+        """
+        Tests the use of if-statements in calc
+        """
+        self._input.setInputFeed([-3, -1, 0, 1, 9])
+        new_vars = lab6.exec_program(self._if_prog)
+        self.assertEqual(new_vars, {"zero": 0, "pos": 1, "nonpos": -1, "x": -3})
+        self.assertEqual(self._output.readline(), "Enter value for x: ")
+        self.assertEqual(self._output.readline(), "nonpos = -1\n")
+        new_vars = lab6.exec_program(self._if_prog)
+        self.assertEqual(new_vars, {"zero": 0, "pos": 1, "nonpos": -1, "x": -1})
+        self.assertEqual(self._output.readline(), "Enter value for x: ")
+        self.assertEqual(self._output.readline(), "nonpos = -1\n")
+        new_vars = lab6.exec_program(self._if_prog)
+        self.assertEqual(new_vars, {"zero": 0, "pos": 1, "nonpos": -1, "x": 0})
+        self.assertEqual(self._output.readline(), "Enter value for x: ")
+        self.assertEqual(self._output.readline(), "zero = 0\n")
+        new_vars = lab6.exec_program(self._if_prog)
+        self.assertEqual(new_vars, {"zero": 0, "pos": 1, "nonpos": -1, "x": 1})
+        self.assertEqual(self._output.readline(), "Enter value for x: ")
+        self.assertEqual(self._output.readline(), "pos = 1\n")
+        new_vars = lab6.exec_program(self._if_prog)
+        self.assertEqual(new_vars, {"zero": 0, "pos": 1, "nonpos": -1, "x": 9})
+        self.assertEqual(self._output.readline(), "Enter value for x: ")
+        self.assertEqual(self._output.readline(), "pos = 1\n")
+
+        # Test if_set
+        self._input.setInputFeed([-3, 0, 1])
+        new_vars = lab6.exec_program(self._if_set_prog)
+        self.assertEqual(new_vars, {"x": -3, "a": -1})
+        self.assertEqual(self._output.readline(), "Enter value for x: ")
+
+        new_vars = lab6.exec_program(self._if_set_prog)
+        self.assertEqual(new_vars, {"x": 0, "a": 0})
+        self.assertEqual(self._output.readline(), "Enter value for x: ")
+
+        new_vars = lab6.exec_program(self._if_set_prog)
+        self.assertEqual(new_vars, {"x": 1, "a": 1})
+        self.assertEqual(self._output.readline(), "Enter value for x: ")
+
+    def testLoop(self):
+        """
+        Tests the use of loops in calc
+        """
+        self._input.setInputFeed([4, 1, 0, -1, 2])
+        new_vars = lab6.exec_program(self._loop_prog)
+        self.assertEqual(new_vars, {"n": 0, "sum": 10})
+        self.assertEqual(self._output.readline(), "Enter value for n: ")
+        self.assertEqual(self._output.readline(), "sum = 10\n")
+        new_vars = lab6.exec_program(self._loop_prog)
+        self.assertEqual(new_vars, {"n": 0, "sum": 1})
+        self.assertEqual(self._output.readline(), "Enter value for n: ")
+        self.assertEqual(self._output.readline(), "sum = 1\n")
+        new_vars = lab6.exec_program(self._loop_prog)
+        self.assertEqual(new_vars, {"n": 0, "sum": 0})
+        self.assertEqual(self._output.readline(), "Enter value for n: ")
+        self.assertEqual(self._output.readline(), "sum = 0\n")
+        new_vars = lab6.exec_program(self._loop_prog)
+        self.assertEqual(new_vars, {"n": -1, "sum": 0})
+        self.assertEqual(self._output.readline(), "Enter value for n: ")
+        self.assertEqual(self._output.readline(), "sum = 0\n")
+
+        # Test once with an expression in the condition
+        new_vars = lab6.exec_program(self._loop_with_binexpr_prog)
+        self.assertEqual(new_vars, {"n": 1, "sum": 2})
+        self.assertEqual(self._output.readline(), "Enter value for n: ")
+        self.assertEqual(self._output.readline(), "sum = 2\n")
+
+
+if __name__ == "__main__":
+    arg_parser = ArgumentParser()
+    arg_parser.add_argument(
+        "--test", choices=["a", "A", "alt", "ALT"], default="", required=False
+    )
+    arg_parser.add_argument("file")
+    args = arg_parser.parse_args()
+    if args.file.rfind("/") != -1:
+        sys.path.append(args.file[: args.file.rfind("/")])
+        potential_name = args.file[args.file.rfind("/") + 1 :]
+    else:
+        sys.path.append(".")
+        potential_name = args.file
+    if potential_name.rfind("."):
+        name = potential_name[: potential_name.rfind(".")]
+    else:
+        name = potential_name
+    try:
+        lab6 = SourceFileLoader(name, args.file).load_module()
+    except FileNotFoundError:
+        print("Could not import lab: " + args.file)
+        print("See traceback for further information:")
+        print()
+        stack_trace = format_exc().split("\n")
+        importlib_has_started = False
+        importlib_has_ended = False
+        for line in stack_trace:
+            if (
+                not importlib_has_ended
+                and importlib_has_started
+                and line.lstrip().startswith("File")
+                and "importlib" not in line
+            ):
+                importlib_has_ended = True
+            if importlib_has_ended:
+                print(line)
+            elif (
+                not importlib_has_started
+                and line.lstrip().startswith("File")
+                and "importlib" in line
+            ):
+                importlib_has_started = True
+        exit(1)
+    if args.test.upper() == "A" or args.test == "":
+        res = (
+            TextTestRunner(verbosity=2)
+            .run(defaultTestLoader.loadTestsFromTestCase(TestEvalProgram))
+            .wasSuccessful()
+        )
+    elif args.test.upper() == "ALT":
+        res = (
+            TextTestRunner(verbosity=2)
+            .run(defaultTestLoader.loadTestsFromTestCase(TestAltEvalProgram))
+            .wasSuccessful()
+        )
+
+    else:
+        print("Unknown arguemnt for --test: " + args.test)
+        exit(2)
+    if res:
+        print("The code passed all the tests")
diff --git a/lab7/Lab7a.py b/lab7/Lab7a.py
new file mode 100644
index 0000000000000000000000000000000000000000..c43df43c7cebd5fe48e6b92e6b17dd046e0b37b5
--- /dev/null
+++ b/lab7/Lab7a.py
@@ -0,0 +1,94 @@
+from books import *
+
+
+def match(pattern,boken):
+    '''En funktion som kollar om ett mönster matchar med något i boken'''
+   
+    #Om mönstret är tomt, returnera True om boken är tom, annars returnera False
+    if not pattern:
+        return not boken
+    
+    #Testa om den första element i mönstret är '--' 
+    elif pattern[0] == '--':
+        
+        #Om resten av mönstret matchar boken, returnera True        
+        if match(pattern[1:], boken):
+            return True
+        
+        # Om boken är tom, returnera False
+        elif not boken:
+            return False
+        
+        #Om längden av mönstret är 1, returnera True
+        elif len(pattern) == 1:
+            return True
+        
+        #Sök igenom resten av boken med samma mönster
+        else:
+            return match(pattern, boken[1:])
+   
+    #Om boken är tomt, returnera True om mönstret är tom, annars returnera False
+    elif not boken:
+        return not pattern
+    
+    # Om '&' är först i mönstret, jämför resten av boken med resten av mönstret
+    elif pattern[0] == '&':
+        return match(pattern[1:], boken[1:])
+    
+    #Testa om den första element i mönstret och första element i boken är listor
+    elif type(pattern[0]) == list and type(boken[0]) == list:
+        
+        #Testa om första element i mönstret och första element i boken matchar varandra,
+        #i så fall testa om resterande element i mönstret och boken matchar varandra , annars returnera False
+        if match(pattern[0],boken[0]) == True:
+            return match(pattern[1:],boken[1:])
+        else:
+            return False 
+        
+    #Testa om den första element i mönstret och första element i boken är lika med varandra, 
+    elif pattern[0] == boken[0]:
+
+        #I så fall testa om resterande element i mönstret och boken matchar varandra 
+        return match(pattern[1:], boken[1:])
+    
+    #I alla andra fall, returnera False
+    else:
+        return False
+
+
+    
+   
+def search(pattern,boken):
+    '''En funktion som hittar den eftersökta rad i boken , genom att jämför ett mönster med boken  '''
+    The_list= []
+    #Dela boken till 5 olika delat
+    for element in boken:
+            
+        #Om mösntret matchar med något del i boken, returnera denna delen
+        if match(pattern,element) == True:
+            The_list.append(element)
+    
+    return The_list
+
+    
+    
+    
+
+                
+            
+''' exempler  '''
+
+#1. search(['--'], db) --> hela db
+
+#2. search(['&'], db) --> []
+
+#3.search(['--',['år', 2010]], [[['författare', ['john', 'zelle']],['titel', ['python', 'programming', 'an', 'introduction', 'to','computer', 'science']],['år', 2010]],
+    #[['författare',['armen', 'asratian']],['titel', ['diskret', 'matematik']],['år', 2010]]]) --> [[['författare', ['john', 'zelle']],['titel', ['python', 'programming', 'an', 'introduction', 'to','computer', 'science']],['år', 2010]],
+    #[['författare',['armen', 'asratian']],['titel', ['diskret', 'matematik']],['år', 2010]]]
+
+#4.search([['författare', ['anders','&']],'--',['år', '&']], db) --> [[['författare', ['anders', 'haraldsson']], ['titel', ['programmering', 'i', 'lisp']], ['år', 1993]]]
+
+#5.search([['författare', ['&', '&', '&']], '--'], db) --> [[['författare', ['j', 'glenn', 'brookshear']], ['titel', ['computer', 'science', 'an', 'overview']], ['år', 2011]]]
+                
+
+    
diff --git a/lab7/books.py b/lab7/books.py
new file mode 100644
index 0000000000000000000000000000000000000000..31c09348ed0b317a52e6330a556ac97a5e4b0ca0
--- /dev/null
+++ b/lab7/books.py
@@ -0,0 +1,19 @@
+# Encoding: ISO-8859-1
+
+db = [[['f�rfattare', ['john', 'zelle']],
+       ['titel', ['python', 'programming', 'an', 'introduction', 'to',
+                  'computer', 'science']],
+       ['�r', 2010]],
+      [['f�rfattare', ['armen', 'asratian']],
+       ['titel', ['diskret', 'matematik']],
+       ['�r', 2012]],
+      [['f�rfattare', ['j', 'glenn', 'brookshear']],
+       ['titel', ['computer', 'science', 'an', 'overview']],
+       ['�r', 2011]],
+      [['f�rfattare', ['john', 'zelle']],
+       ['titel', ['data', 'structures', 'and', 'algorithms', 'using',
+                  'python', 'and', 'c++']],
+       ['�r', 2009]],
+      [['f�rfattare', ['anders', 'haraldsson']],
+       ['titel', ['programmering', 'i', 'lisp']],
+       ['�r', 1993]]]
diff --git a/lab7/lab7b.py b/lab7/lab7b.py
new file mode 100644
index 0000000000000000000000000000000000000000..20d3e952d35d39cb3deea192caefd4f356a3c3a3
--- /dev/null
+++ b/lab7/lab7b.py
@@ -0,0 +1,119 @@
+from tree import *
+#########  Deluppgift (i)  ############
+def traverse(tree,inner_node_fn, leaf_fn, empty_tree_fn):
+    ''' En funktion som tar ett binärt sökträd och tre funktioner'''
+    
+    #Testar om trädet är tomt och i så fall ge tillbaka noll
+    if is_empty_tree(tree):
+        return empty_tree_fn()
+
+    #Testar om den skickade trädet är en löv, alltså testar om det är en siffra. I så fall skicka lövet till funktionen "leaf_fn"
+    elif is_leaf(tree):
+        return leaf_fn(tree)
+
+    #spara värdet av vänsrta subtree i "left_value", värdet av högra subtree i "right_value" och noden i "nod"
+    left_value =  traverse(left_subtree(tree),inner_node_fn, leaf_fn, empty_tree_fn)
+    right_value = traverse(right_subtree(tree),inner_node_fn, leaf_fn, empty_tree_fn)
+    nod = rotnod(tree)
+    return inner_node_fn(nod, left_value, right_value)
+
+
+ 
+
+#########  Deluppgift (ii)  ############
+
+
+def contains_key(key,tree):
+    '''Testar om en "key" finns i trädet'''
+    
+    def empty_tree_fn():
+        '''En funktion som ger tillbaka noll'''
+        return 0
+
+    def leaf_fn(leaf):
+        '''En funktion som tar ett löv och jämför det med "key" '''
+        #Testar om lövet är lika med "key" 
+        return leaf == key
+             
+    def inner_node_fn(nod, left_value, right_value):
+        '''En funktion som jämför noden med "key"'''
+        
+        #Testar om noden är lika med "key" eller om värdet av högra/vänstra lövet är sant 
+        if key == nod or left_value or right_value:
+            return True
+        else:
+            return False
+    
+    return traverse(tree, inner_node_fn, leaf_fn, empty_tree_fn)
+    
+    
+#########  Deluppgift (iii)  ############
+
+def tree_size(tree):
+    '''En funktion som mäter storleken av ett träd'''
+    
+    def empty_tree_fn():
+        '''En funktion som ger tillbaka noll'''
+        return 0
+
+    
+    def leaf_fn(leaf):
+        '''En funktion som räknar antal gånger den körs'''
+        return 1
+    
+        
+    def inner_node_fn(nod, left_value, right_value):
+        '''En funktion som tar värdet av vänstar respektive högra subtree och adderar de ihop plus 1'''
+        return 1 + left_value +  right_value
+
+    return traverse(tree, inner_node_fn, leaf_fn, empty_tree_fn)
+    
+    
+#########  Deluppgift (iv)  ############
+
+def tree_depth(tree):
+    '''En funktion som mäter hur djupt ett träd är'''
+
+    def empty_tree_fn():
+        '''En funktion som ger tillbaka noll'''
+        return 0
+
+    def leaf_fn(leaf):
+        '''En funktion som räknar antal gånger den körs'''
+        return 1
+        
+            
+        
+    def inner_node_fn(nod, left_value, right_value):
+        '''En funktion som jämför väntra och högra subtree med varandra'''
+
+        
+        #Testar om väntra subtree är djupare än högra subtree. Isf addera 1 till väntra subtree
+        if left_value >= right_value:
+            return 1 + left_value
+
+        #Testar om högra subtree är djupare än väntra subtree. Isf addera 1 till högra subtree
+        else:
+            return 1 + right_value
+
+
+    return traverse(tree, inner_node_fn, leaf_fn, empty_tree_fn)
+        
+        
+        
+'''exempler för contains_key '''
+#contains_key(8, [6, 7, [[],8,[]]]) --> True
+#contains_key(2, [[], 1, [3,4,[7,9,[[1,2,6],5,3]]]]) --> True
+#contains_key(6,[[],9,[]]) --> False
+#contains_key(2,[[1,1,[1,1,1]],1,[1,1,[1,1,[1,2,[1,1,[[[1,1,1],1,1],1,1]]]]]]) --> True
+
+'''exempler för tree_size '''
+#tree_size([2, 7, [[1,2,3],2,[[1,2,[1,2,3]],2,[[],2,[]]]]]) --> 13
+#tree_size([[],1,[]]) --> 1
+#tree_size([[1,1,[1,1,1]],1,[1,1,[1,1,[1,2,[1,1,[[[1,1,1],1,1],1,1]]]]]]) --> 21
+
+'''exempler för tree_depth '''
+#tree_depth([[1,1,[1,1,1]],1,[1,1,[1,1,[1,2,[1,1,[[[1,1,1],1,1],1,1]]]]]]) --> 9
+#tree_depth([[],9,[]]) --> 1
+#tree_depth([6, 7, [[],8,[]]]) --> 2
+
diff --git a/lab7/labb7.py b/lab7/labb7.py
new file mode 100644
index 0000000000000000000000000000000000000000..c25a66d89ef68fc37165e2971726075a75134c54
--- /dev/null
+++ b/lab7/labb7.py
@@ -0,0 +1,282 @@
+def matchhhhh(seq, pattern):
+    """
+    Returns whether given sequence matches the given pattern
+    """
+    if not pattern:
+        return not seq
+    elif pattern[0] == '--':
+        if match(seq, pattern[1:]):
+            return True
+        elif not seq:
+            return False
+        else:
+            return match(seq[1:], pattern)
+    elif not seq:
+        return False
+    elif pattern[0] == '&':
+        return match(seq[1:], pattern[1:])
+    elif seq[0] == pattern[0]:
+        return match(seq[1:], pattern[1:])
+    else:
+        return False
+    
+    
+    
+db =[
+    
+ [['författare', ['john', 'zelle']],
+  ['titel', ['python', 'programming', 'an', 'introduction', 'to', 'computer', 'science']],
+  ['år', 2010]],
+ 
+ [['författare', ['armen', 'asratian']],
+  ['titel', ['diskret', 'matematik']],
+  ['år', 2012]],
+ 
+ [['författare', ['j', 'glenn', 'brookshear']],
+  ['titel', ['computer', 'science', 'an', 'overview']],
+  ['år', 2011]],
+ 
+ [['författare', ['john', 'zelle']],
+  ['titel', ['data', 'structures', 'and', 'algorithms', 'using', 'python', 'and', 'c++']],
+  ['år', 2009]],
+ 
+ [['författare', ['anders', 'haraldsson']],
+  ['titel', ['programmering', 'i', 'lisp']],
+  ['år', 1993]]
+ 
+ ]
+
+def match(pattern,db):
+    if not pattern:
+        return not db
+    if len(pattern) > 1 and len(db) == 1:
+        return False
+    
+    elif type(pattern[0]) == list and type(db[0]) == list:
+        return match(pattern[0],db[0])
+        
+    elif pattern[0] == db[0]:
+        return match(pattern[1:], db[1:])
+        
+    elif pattern[0] == '--':
+       # print((pattern,db))
+        if match(pattern[1:], db[1:]):
+            
+            return True
+        elif not db:
+            return False
+        elif len(pattern) == 1:
+            return True
+        
+        else:
+            return match(pattern, db[1:])
+
+    elif pattern[0] == '&':
+        return match(pattern[1:], db[1:])
+     
+        
+    else:
+        return False
+
+
+def författare(pattern,db):
+    a = []
+    for element in db:
+        for elem in element:
+            if 'författare' in elem:
+                a.append(match(pattern,elem))
+                
+    return a
+                    
+                
+
+
+def titel(pattern,db):
+    a = []
+    for element in db:
+        for elem in element:
+            if 'titel' in elem:
+                a.append(match(pattern,elem))
+                
+    return a
+        
+    
+def år(pattern,db):
+    a=[]
+    
+    for element in db:
+        for elem in element:
+            if 'år' in elem:
+                a.append(match(pattern,elem))
+                
+    return a
+
+
+
+
+def search(pattern,db):
+    a=[]
+    for element in pattern:
+        if 'författare' in element:
+            a.append('g')
+            a=författare(element,db)
+            
+            return search(pattern[1:],db)
+        
+        elif 'titel' in element:
+            
+            b=titel(element,db)
+            
+            return search(pattern[1:],db)
+        
+        elif 'år' in element:
+            if isinstance(element[1],int):
+                
+                c=år(element,db)
+                
+                
+            
+    return a
+    
+   # for element in pattern:
+    #    for elem in db:
+     #       for i in elem:
+                
+      #          if match(element,i) == True:
+       #             print('true',element,'\\\\\\',i)
+        #            return search(pattern,db[1:])
+                    
+         #       elif match(element,i) == False:
+          #          print('false',element,'\\\\\\',i)
+                    
+                #else:
+                 #   return match(element,i[1:])
+                    
+        #    if författare(element[1],db) == True:
+         #       for element in pattern:
+                     
+     #   search([['författare', ['&', 'zelle']], ['titel', ['--', 'python', '--']], ['år', '&']], db)
+    
+
+def searchhhhhh(pattern,db):
+    pattern1=[]
+    db0=[]
+    db1=[]
+    db2=[]
+    db3=[]
+    db4=[]
+    
+    print(db[0])
+    print(db[0][0])
+    print(db[0][1])
+    print(db[0][2])
+    print('££££££££££££££££££££££££££££££££££££££££££££££')
+   
+    
+    print(pattern[0])
+    print(pattern[1])
+    print(pattern[2])
+    print('zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz')
+    for element in pattern:
+        for i in element:
+            if type(i) == list:
+                for y in i:
+                    pattern1.append(y)
+            else:
+                pattern1.append(i)
+                
+    for k in db[0]:
+        for i in k:
+            if type(i) == list:
+                for y in i:
+                    db0.append(y)
+            else:
+                db0.append(i)
+    
+    for kk in db[1]:
+        for i in kk:
+            if type(i) == list:
+                for y in i:
+                    db1.append(y)
+            else:
+                db1.append(i)
+    
+    for kkk in db[2]:
+        for i in kkk:
+            if type(i) == list:
+                for y in i:
+                    db2.append(y)
+            else:
+                db2.append(i)
+                
+    for kkkk in db[3]:
+        for i in kkkk:
+            if type(i) == list:
+                for y in i:
+                    db3.append(y)
+            else:
+                db3.append(i)
+    for kkkkk in db[4]:
+        for i in kkkkk:
+            if type(i) == list:
+                for y in i:
+                    db4.append(y)
+            else:
+                db4.append(i)
+                
+    print((pattern1,db3))
+    
+    
+    if match(pattern1,db0) == True:
+        return db[0]
+    elif match(pattern1,db1) == True:
+        return db[1]
+    elif match(pattern1,db2) == True:
+        return db[2]
+    elif match(pattern1,db3) == True:
+        return db[3]
+    elif match(pattern1,db4) == True:
+        return db[4]
+            
+            
+    
+    #if match(pattern1,db3):
+     #   return db[3]
+    #for o in pattern1:
+       # if o in db0:
+        #    print(db0.index(o))
+            
+     #   if o in db4:
+      #      print(db4.index(o))
+                
+    #return db0 , db1 , db2 , db3 , db4
+    
+  #  for u in k:
+           # for ii in u:
+            #    print('yyyy',ii)
+            #    if type(ii) == list:
+             #       for yy in ii:
+              #          dbb.append(yy)
+               # else:
+                #    dbb.append(ii)
+                    
+    
+      
+   
+           
+             
+            
+            
+            
+            
+            
+            
+            
+            
+            
+#if '&' in i:
+    #            if 
+     #           print('&&&&',i)
+    
+
+
diff --git a/lab7/match.py b/lab7/match.py
new file mode 100644
index 0000000000000000000000000000000000000000..6f76e4c313f38ca00d693d52a9a85f78f2949ccd
--- /dev/null
+++ b/lab7/match.py
@@ -0,0 +1,21 @@
+def match(seq, pattern):
+    """
+    Returns whether given sequence matches the given pattern
+    """
+    if not pattern:
+        return not seq
+    elif pattern[0] == '--':
+        if match(seq, pattern[1:]):
+            return True
+        elif not seq:
+            return False
+        else:
+            return match(seq[1:], pattern)
+    elif not seq:
+        return False
+    elif pattern[0] == '&':
+        return match(seq[1:], pattern[1:])
+    elif seq[0] == pattern[0]:
+        return match(seq[1:], pattern[1:])
+    else:
+        return False
diff --git a/lab7/tree.py b/lab7/tree.py
new file mode 100644
index 0000000000000000000000000000000000000000..e18af0a54173208d2e02bba0b9d531e6d98b3426
--- /dev/null
+++ b/lab7/tree.py
@@ -0,0 +1,26 @@
+# encoding: iso-8859-1
+
+# TDDE23 Lab 4: Binary tree
+
+def is_empty_tree(tree):
+    return isinstance(tree, list) and not tree
+
+
+def is_leaf(tree):
+    return isinstance(tree, int)
+
+
+def create_tree(left_tree, key, right_tree):
+    return [left_tree, key, right_tree]
+
+    
+def left_subtree(tree):
+    return tree[0]
+
+
+def right_subtree(tree):
+    return tree[2]
+
+def rot_nod(tree):
+    return tree[1]
+
diff --git a/lab8/cal_abstraction.py b/lab8/cal_abstraction.py
new file mode 100644
index 0000000000000000000000000000000000000000..4b20dc3239827efb03a67ebffceba7472e4c3492
--- /dev/null
+++ b/lab8/cal_abstraction.py
@@ -0,0 +1,958 @@
+# =========================================================================
+#  Calendar - Functional and imperative programming in Python
+#
+#  Module: cal_abstraction.py
+#  Updated: 2005-09-27 by Peter Dalenius
+#    Translated to Python in 2012 by Peter L-G
+#    Translated to English in 2013 by Anders M.L.
+#    Changes in 2020 by Jonas K (NamedTuple, type hints, ...)
+#  Dependencies:
+#    None
+# =========================================================================
+
+# This module contains the definitions of the data types used by
+# the calendar, including both the actual type definitions
+# (usually using NamedTuple) and the low-level primitives that
+# are used for manipulating the associated data.
+
+# The module also contains a number of useful functions that are
+# related to these datatypes, but that do not actually depend on
+# how the datatypes are represented.  These should be clearly
+# separated in the code below.
+
+from typing import (
+        Type, NamedTuple, List, Callable, Set, Dict, Any,
+        get_origin, get_args
+)
+
+# =========================================================================
+#  1. Basic functions for ensuring correctness.
+# =========================================================================
+from settings import USE_DEFAULT_DURATION_TYPE, USE_DEFAULT_TIMESPAN_TYPE
+
+
+# =========================================================================
+#  1. General machinery for testing types and other conditions.
+# =========================================================================
+
+def ensure(val, pred) -> None:
+    """Assert that the given value satisfies the given predicate."""
+    assert pred(val), f"Value {val} does not satisfy constraints."
+
+
+def ensure_type(val, some_type: Type) -> None:
+    """
+        Assert that the given value is of the given type.
+
+        Only handles X, List[X], Dict[X, Y]. Where X and Y are types handled by
+        `ensure_type()` or are "simple" types.
+
+        Some examples of "simple" types are: str, int, float, bool.
+    """
+    origin = get_origin(some_type)
+    if origin is not None:
+        # This is a nested type.
+        assert type(val) is origin, f"Value {val} is of type {type(val)}; " \
+                                    f"expected type {some_type}."
+        args = get_args(some_type)
+        if args and origin is list:
+            element_type = args[0]
+            for x in val:
+                ensure_type(x, element_type)
+
+        elif args and origin is dict:
+            key_type, value_type = args
+            for x in val.keys():
+                ensure_type(x, key_type)
+
+            for x in val.values():
+                ensure_type(x, value_type)
+
+        else:
+            assert False, f"Cannot check the given type {some_type}"
+    elif some_type is not Any:
+        # 'Simple' type.
+        assert type(val) is some_type, f"Value {val} is of type {type(val)}; " \
+                                       f"expected type {some_type}."
+
+
+# =========================================================================
+#  2. Timepoints
+# =========================================================================
+
+# ----- HOUR -----
+
+Hour = NamedTuple("Hour", [("number", int)])
+
+
+def new_hour(number: int) -> Hour:
+    """Create and return a new Hour with the given (non-negative) number."""
+    ensure_type(number, int)
+
+    # This is used both as a timepoint and as the length of an interval.
+    # Therefore we should NOT test that the number of hours is at most 23.
+    ensure(number, lambda h: 0 <= h)
+
+    return Hour(number=number)
+
+
+def hour_number(hour: Hour) -> int:
+    """Return the number of the given Hour."""
+    ensure_type(hour, Hour)
+    return hour.number
+
+
+# ----- MINUTE -----
+
+Minute = NamedTuple("Minute", [("number", int)])
+
+
+def new_minute(number: int) -> Minute:
+    """Create and return a new Minute with the given (non-negative) number."""
+    ensure_type(number, int)
+
+    # This is used both as a timepoint and as the length of an interval.
+    # Therefore we should NOT test that the number of minutes is at most 59.
+    ensure(number, lambda m: 0 <= m)
+
+    return Minute(number=number)
+
+
+def minute_number(m: Minute) -> int:
+    """Return the number of the given Minute."""
+    ensure_type(m, Minute)
+    return m.number
+
+
+# ---- Time ----
+
+Time = NamedTuple("Time", [("hour", Hour), ("minute", Minute)])
+
+
+def new_time(hour: Hour, minute: Minute) -> Time:
+    """
+    Create and return a new Time with the given Hour and Minute, which
+    must correspond to a valid 24-hour timepoint.
+    """
+    ensure_type(hour, Hour)
+    ensure_type(minute, Minute)
+    ensure(hour, lambda h: 0 <= hour_number(h) <= 23)
+    ensure(minute, lambda m: 0 <= minute_number(m) <= 59)
+    return Time(hour, minute)
+
+
+def time_hour(time: Time) -> Hour:
+    """Return the hour of a Time value"""
+    ensure_type(time, Time)
+    return time.hour
+
+
+def time_minute(time: Time) -> Minute:
+    """Return the minute of a Time value"""
+    ensure_type(time, Time)
+    return time.minute
+
+
+# ---- Time: Functionality independent of the representation ----
+
+def new_time_from_string(s: str) -> Time:
+    """
+    Create and return a new Time with the given Hour and Minute,
+    given in the 5-character 'HH:MM' format (for example, '12:34')
+    """
+    hour = int(s[0:2])
+    minute = int(s[3:5])
+    return new_time(new_hour(hour), new_minute(minute))
+
+
+def time_precedes(t1: Time, t2: Time) -> bool:
+    """Return true iff t1 is strictly before t2"""
+    hour1 = hour_number(time_hour(t1))
+    hour2 = hour_number(time_hour(t2))
+    min1 = minute_number(time_minute(t1))
+    min2 = minute_number(time_minute(t2))
+    return (hour1, min1) < (hour2, min2)
+
+
+def time_equals(t1: Time, t2: Time) -> bool:
+    """Return true iff t1 and t2 refer represent the same time."""
+    hour1 = hour_number(time_hour(t1))
+    hour2 = hour_number(time_hour(t2))
+    min1 = minute_number(time_minute(t1))
+    min2 = minute_number(time_minute(t2))
+    return (hour1, min1) == (hour2, min2)
+
+
+def time_precedes_or_equals(t1: Time, t2: Time) -> bool:
+    """Return true iff t1 is before t2 or at the same time."""
+    return time_precedes(t1, t2) or time_equals(t1, t2)
+
+
+def time_latest(t1: Time, t2: Time) -> Time:
+    """Return the later of two timepoints"""
+    if time_precedes(t1, t2):
+        return t2
+    else:
+        return t1
+
+
+def time_earliest(t1: Time, t2: Time) -> Time:
+    """Return the earliest of two timepoints"""
+    if time_precedes(t1, t2):
+        return t1
+    else:
+        return t2
+
+
+# =========================================================================
+#  4. Time spans (intervals) and their durations
+# =========================================================================
+
+# ---- TimeSpan ----
+
+# TimeSpans have two different representations.
+
+if USE_DEFAULT_TIMESPAN_TYPE:
+    # We want to use the default implementation of the TimeSpan type, with
+    # a NamedTuple having two fields called 'start' and 'end'.  Then we also
+    # need the corresponding implementations of the lowest level functions
+    # new_time_span(), ts_start(), and ts_end().  These are the only three
+    # functions that should depend on the actual internal representation of
+    # the TimeSpan type!
+    TimeSpan = NamedTuple("TimeSpan", [("start", Time), ("end", Time)])
+
+
+    def new_time_span(start: Time, end: Time) -> TimeSpan:
+        """
+        Create and return a new TimeSpan with the given start and end time.
+        The start time must strictly precede the end time.
+        """
+        ensure_type(start, Time)
+        ensure_type(end, Time)
+        if time_equals(start, end):
+            raise ValueError(f"Start and end time are the same, {start}, {end}.")
+        elif not time_precedes(start, end):
+            raise ValueError(f"Start time {start} must strictly precede the end time {end}.")
+        else:
+            return TimeSpan(start, end)
+
+
+    def ts_start(ts: TimeSpan) -> Time:
+        """Return the start of a TimeSpan"""
+        ensure_type(ts, TimeSpan)
+        return ts.start
+
+
+    def ts_end(ts: TimeSpan) -> Time:
+        """Return the end of a TimeSpan"""
+        ensure_type(ts, TimeSpan)
+        return ts.end
+
+else:
+    # We want to use an alternative dictionary-based representation for TimeSpans.
+    # Again, we need to define TimeSpan and its three associated lower level
+    # functions; as long as abstraction principles are followed correctly, we
+    # should NOT need to change anything else.
+    class TimeSpan(dict):
+        """
+        A dictionary representation of TimeSpans.
+        """
+        pass
+
+
+    def new_time_span(start: Time, end: Time) -> TimeSpan:
+        """
+        Create and return a new TimeSpan with the given start and end time.
+        The start time must strictly precede the end time.
+        """
+        ensure_type(start, Time)
+        ensure_type(end, Time)
+        if time_equals(start, end):
+            raise ValueError(f"Start and end time are the same, {start}, {end}.")
+        elif not time_precedes(start, end):
+            raise ValueError(f"Start time {start} must strictly precede the end time {end}.")
+        else:
+            return TimeSpan(dict(start=start, end=end))
+
+
+    def ts_start(ts: TimeSpan) -> Time:
+        """Return the start of a TimeSpan"""
+        ensure_type(ts, TimeSpan)
+        return ts["start"]
+
+
+    def ts_end(ts: TimeSpan) -> Time:
+        """Return the end of a TimeSpan"""
+        ensure_type(ts, TimeSpan)
+        return ts["end"]
+
+# ---- TimeSpan: Functionality independent of the representation ----
+
+# Some functions from TimeSpan and Duration need to be modified in lab8a
+# and are therefore placed in lab8a.py, so *this* file can remain unchanged.
+# That file is imported later in this file.
+
+
+# ---- Duration ----
+
+if USE_DEFAULT_DURATION_TYPE:
+    # We want to use the default implementation of the Duration type.
+    # See also TimeSpan comments above.
+    Duration = NamedTuple("Duration", [("hour", Hour), ("minute", Minute)])
+
+
+    def new_duration(hour: Hour, minute: Minute) -> Duration:
+        """Create a duration corresponding to given number of hours and minutes.
+        You may specify more than 59 minutes; any multiple of 60 minutes will be
+        converted to hours."""
+        return Duration(Hour(hour_number(hour) + minute_number(minute) // 60),
+                        Minute(minute_number(minute) % 60))
+
+
+    def duration_hour(dur: Duration) -> Hour:
+        """Return the number of whole hours in a Duration"""
+        ensure_type(dur, Duration)
+        return dur.hour
+
+
+    def duration_minute(dur: Duration) -> Minute:
+        """Return the number of minutes in a Duration
+        (0 to 59, not including whole hours)"""
+        ensure_type(dur, Duration)
+        return dur.minute
+
+else:
+    # We want to use the alternative implementation of the Duration type.
+    # See also TimeSpan comments above.
+
+    Duration = NamedTuple("Duration", [("minutes", Minute)])
+
+
+    def new_duration(hour: Hour, minute: Minute) -> Duration:
+        """Create a duration corresponding to given number of hours and minutes.
+        You may specify more than 59 minutes; any multiple of 60 minutes will be
+        converted to hours."""
+        return Duration(new_minute(hour_number(hour) * 60 + minute_number(minute)))
+
+
+    def duration_hour(dur: Duration) -> Hour:
+        """Return the number of whole hours in a Duration"""
+        ensure_type(dur, Duration)
+        return new_hour(minute_number(dur.minutes) // 60)
+
+
+    def duration_minute(dur: Duration) -> Minute:
+        """Return the number of minutes in a Duration
+        (0 to 59, not including whole hours)"""
+        ensure_type(dur, Duration)
+        return new_minute(minute_number(dur.minutes) % 60)
+
+# ---- Duration: Functionality independent of the representation ----
+
+# Some functions from TimeSpan and Duration need to be modified in lab8a
+# and are therefore placed in lab8a.py, so *this* file can remain unchanged.
+#
+# Yes, PEP8 says we shouldn't import anything in the middle of a file,
+# but this lab is a special case that is quite different from actual
+# production code.
+from lab8a import *
+
+
+def new_duration_from_string(s: str) -> Duration:
+    """Create a duration corresponding to the string "HH:MM". The string must be
+     well-formed and the number of hours can consist of an arbitrary number of digits."""
+    pos = s.find(":")
+    hour = int(s[0:pos])
+    minute = int(s[pos + 1:])
+    return new_duration(new_hour(hour), new_minute(minute))
+
+
+# =========================================================================
+#  5. Days, months and dates
+# =========================================================================
+
+# ---- Day ----
+
+Day = NamedTuple("Day", [("number", int)])
+
+
+def new_day(number: int) -> Day:
+    """Create a Day (of the month) with the given number."""
+    ensure_type(number, int)
+    ensure(number, lambda d: 1 <= d <= 31)
+    return Day(number=number)
+
+
+def day_number(day: Day) -> int:
+    """Return the day number of the given Day (of the month)."""
+    ensure_type(day, Day)
+    return day.number
+
+
+# ---- Day: Functionality independent of the representation ----
+
+# None at this point
+
+# ---- Month ----
+
+Month = NamedTuple("Month", [("name", str)])
+
+# We ignore leap years here...
+MONTHS = {
+    ("jan", "january", 31, 1),
+    ("feb", "february", 28, 2),
+    ("mar", "march", 31, 3),
+    ("apr", "april", 30, 4),
+    ("may", "may", 31, 5),
+    ("jun", "june", 30, 6),
+    ("jul", "july", 31, 7),
+    ("aug", "august", 31, 8),
+    ("sep", "september", 30, 9),
+    ("oct", "october", 31, 10),
+    ("nov", "november", 30, 11),
+    ("dec", "december", 31, 12),
+}
+
+# Using the specification above, we can easily create a number of associated sets
+# and mappings/dictionaries...
+
+MONTH_FULL_NAMES = {spec[1] for spec in MONTHS}  # type: Set[str]
+MONTH_ABBREVIATIONS = {spec[0] for spec in MONTHS}  # type: Set[str]
+MONTH_ABBREV_TO_NAME = {spec[0]: spec[1] for spec in MONTHS}  # type: Dict[str, str]
+MONTH_NAME_TO_LENGTH = {spec[1]: spec[2] for spec in MONTHS}  # type: Dict[str, int]
+MONTH_NAME_TO_NUMBER = {spec[1]: spec[3] for spec in MONTHS}  # type: Dict[str, int]
+
+
+def new_month(name: str) -> Month:
+    """Create and return a new Month with the given name.  The name must valid in English."""
+    ensure_type(name, str)
+    if name in MONTH_FULL_NAMES:
+        return Month(name)
+    elif name in MONTH_ABBREVIATIONS:
+        return Month(MONTH_ABBREV_TO_NAME[name])
+    else:
+        raise ValueError(f"'{name}' is not the name of a month.")
+
+
+ALL_MONTHS = [new_month(name) for name in MONTH_FULL_NAMES]  # type: List[Month]
+
+
+def month_name(mon: Month) -> str:
+    """Return the full English name of the given Month"""
+    ensure_type(mon, Month)
+    return mon.name
+
+
+# ---- Month: Functionality independent of the representation ----
+
+
+def month_number(mon: Month) -> int:
+    """Return the number of the given Month (1 to 12)"""
+    ensure_type(mon, Month)
+    return MONTH_NAME_TO_NUMBER[month_name(mon)]
+
+
+def days_in_month(mon: Month) -> int:
+    """Return the number of days in the given Month, ignoring leap years."""
+    ensure_type(mon, Month)
+    return MONTH_NAME_TO_LENGTH[month_name(mon)]
+
+
+# ----- Date -----
+
+Date = NamedTuple("Date", [("day", Day), ("month", Month)])
+
+
+def new_date(day: Day, mon: Month) -> Date:
+    """Create and return a new Date with the given Day and Month."""
+    ensure_type(day, Day)
+    ensure_type(mon, Month)
+    if day_number(day) > days_in_month(mon):
+        raise ValueError(f"Date {day}, {mon} does not conform to specifications.")
+    else:
+        return Date(day, mon)
+
+
+def date_month(date: Date) -> Month:
+    """Return the Month of the given Date"""
+    ensure_type(date, Date)
+    return date.month
+
+
+def date_day(date: Date) -> Day:
+    """Return the Day of the given Date"""
+    ensure_type(date, Date)
+    return date.day
+
+
+# =========================================================================
+# 6. Appointments and their components.
+# =========================================================================
+
+
+# ----- SUBJECT -----
+
+
+Subject = NamedTuple("Subject", [("text", str)])
+
+
+def new_subject(text: str) -> Subject:
+    """Create and return a new Subject with the given subject text."""
+    ensure_type(text, str)
+    return Subject(text)
+
+
+def subject_text(subject: Subject) -> str:
+    """Return the text of the given Subject"""
+    ensure_type(subject, Subject)
+    return subject.text
+
+
+# ----- APPOINTMENT -----
+
+Appointment = NamedTuple("Appointment", [("span", TimeSpan), ("subject", Subject)])
+
+
+def new_appointment(span: TimeSpan, subject: Subject) -> Appointment:
+    """Create and return a new Appointment with the given time span and subject."""
+    ensure_type(span, TimeSpan)
+    ensure_type(subject, Subject)
+    return Appointment(span, subject)
+
+
+def app_span(app: Appointment) -> TimeSpan:
+    """Return the TimeSpan of the given Appointment"""
+    ensure_type(app, Appointment)
+    return app.span
+
+
+def app_subject(app: Appointment) -> Subject:
+    """Return the Subject of the given Appointment"""
+    ensure_type(app, Appointment)
+    return app.subject
+
+
+# =========================================================================
+#  7. Calendars -- days, months and years of *appointments*
+# =========================================================================
+
+
+# ----- CalendarDay -----
+
+CalendarDay = NamedTuple(
+    "CalendarDay", [("day", Day), ("appointments", List[Appointment])]
+)
+
+
+def new_calendar_day(day: Day, appointments: List[Appointment] = None) -> CalendarDay:
+    """
+    Create and return a new CalendarDay for the given Day of the month,
+    with the given appointments.
+    """
+    ensure_type(day, Day)
+    if appointments is None:
+        # If we use [] as a default value above, then every call to this function will
+        # use the *same* list as a default value.  Instead we must use None as
+        # a value, and if something empty is provided, we create a *new* list each time.
+        appointments = []
+    else:
+        ensure_type(appointments, List[Appointment])
+    return CalendarDay(day, appointments)
+
+
+def cd_day(cal_day: CalendarDay) -> Day:
+    """Return the Day (of the month) of the given CalendarDay"""
+    ensure_type(cal_day, CalendarDay)
+    return cal_day.day
+
+
+# We don't provide a way of retrieving the list of appointments.
+# Instead, we provide a way of *iterating* over all appointments.
+
+def cd_iter_appointments(cal_day: CalendarDay):
+    """To be used as `for appointment in cd_iter_appointments(cal_day)"""
+    # For the purpose of TDDE24, you do not necessarily have to know exactly how this works.
+    # The point is that it gives others a way of iterating without knowing the internal
+    # data structures.
+    # If you are interested:  See "generator functions" at https://wiki.python.org/moin/Generators.
+    ensure_type(cal_day, CalendarDay)
+    for appointment in cal_day.appointments:
+        yield appointment
+
+
+def cd_is_empty(cal_day: CalendarDay) -> bool:
+    """Return true iff the given CalendarDay has no appointments."""
+    ensure_type(cal_day, CalendarDay)
+    return not cal_day.appointments
+
+
+def cd_plus_appointment(cal_day: CalendarDay, appointment: Appointment) -> CalendarDay:
+    """
+    Returns a copy of the given CalendarDay, where the given Appointment
+    has been added in its proper position.
+    """
+    ensure_type(appointment, Appointment)
+    ensure_type(cal_day, CalendarDay)
+
+    def add_appointment(app: Appointment, appointments: List[Appointment]):
+        if not appointments or time_precedes(
+                ts_start(app_span(app)), ts_start(app_span(appointments[0]))
+        ):
+            return [app] + appointments
+        else:
+            return [appointments[0]] + add_appointment(app, appointments[1:])
+
+    return new_calendar_day(
+        cd_day(cal_day), add_appointment(appointment, cal_day.appointments)
+    )
+
+
+# ---- CalendarDay: Functionality independent of the representation ----
+
+
+AppointmentPredicate = Callable[[Appointment], bool]
+"""
+This type corresponds to a *function* that takes an Appointment
+as a parameter, and that returns a boolean value (True or False).
+"""
+
+
+def cd_any_appointment_satisfies(
+        cal_day: CalendarDay, predicate: AppointmentPredicate
+) -> bool:
+    """
+    Does any appointment during the given calendar day satisfy the
+    given predicate?
+    :param cal_day: A CalendarDay.
+    :param predicate: A function that takes a particular Appointment
+    as parameter and returns true iff the Appointment satisfies some
+    condition.
+    """
+    ensure_type(cal_day, CalendarDay)
+    return any(predicate(appointment)
+               for appointment in cd_iter_appointments(cal_day))
+
+
+# ----- CalendarMonth  -----
+
+CalendarMonth = NamedTuple(
+    "CalendarMonth", [("month", Month), ("days", List[CalendarDay])]
+)
+"""
+A CalendarMonth contains zero or more CalendarDays containing Appointments.
+The CalendarDays can be accessed ... +++
+"""
+
+
+# CalendarMonth currently happens to store CalendarDays in a list that is
+# sorted on the day number (1 to 31).
+#
+# (This information belongs in a comment and NOT in a docstring, since it is
+# just an explanation of how the *current* implementation works, not a
+# promise that the user should count on!)
+
+
+def new_calendar_month(month: Month, days: List[CalendarDay] = None) -> CalendarMonth:
+    """
+    Create and return a new CalendarMonth for the given month of the year,
+    with the given list of CalendarDays.
+    """
+    ensure_type(month, Month)
+    if days is None:
+        # If we use [] as a default value above, then every call to this function will
+        # use the *same* list as a default value.  Instead we must use None as
+        # a value, and if something empty is provided, we create a *new* list each time.
+        days = []
+    else:
+        ensure_type(days, List[CalendarDay])
+    return CalendarMonth(month, days)
+
+
+def cm_month(cal_month: CalendarMonth) -> Month:
+    """Return the Month of the given CalendarMonth"""
+    ensure_type(cal_month, CalendarMonth)
+    return cal_month.month
+
+
+def cm_iter_days(cal_mon: CalendarMonth):
+    """To be used as `for cal_day in cm_iter_days(cal_month)`.     Iterates over all
+    days in the month, not just those days that have appointments."""
+    # For the purpose of TDDE24, you do not necessarily have to know exactly how this works.
+    # The point is that it gives others a way of iterating without knowing the internal
+    # data structures.
+    # If you are interested:  See "generator functions" at https://wiki.python.org/moin/Generators.
+    ensure_type(cal_mon, CalendarMonth)
+    for daynum in range(1, days_in_month(cm_month(cal_mon)) + 1):
+        yield cm_get_day(cal_mon, new_day(daynum))
+
+
+def cm_is_empty(cal_mon: CalendarMonth) -> bool:
+    """Return true iff the given CalendarMonth has no days."""
+    ensure_type(cal_mon, CalendarMonth)
+    return not cal_mon.days
+
+
+def cm_plus_cd(cal_mon: CalendarMonth, cal_day: CalendarDay) -> CalendarMonth:
+    """
+    Returns a copy of the given CalendarMonth, where the given CalendarDay
+    has been added in its proper position.  If the CalendarMonth already
+    contains a CalendarDay for the same Day, then the old CalendarDay is
+    replaced with the new CalendarDay.
+    """
+    ensure_type(cal_day, CalendarDay)
+    ensure_type(cal_mon, CalendarMonth)
+
+    def add_to(days: List[CalendarDay], day_to_add: CalendarDay) -> List[CalendarDay]:
+        if not days:
+            return [day_to_add]
+
+        next_day = days[0]
+        next_day_number = day_number(cd_day(next_day))
+
+        daynum = day_number(cd_day(day_to_add))
+        if daynum < next_day_number:
+            # Add the new CalendarDay at the current position
+            return [day_to_add] + days
+        elif daynum == next_day_number:
+            # Replace the CalendarDay at the current position with the new CalendarDay
+            return [day_to_add] + days[1:]
+        else:
+            # Need to move further
+            return [next_day] + add_to(days[1:], day_to_add)
+
+    return CalendarMonth(cal_mon.month, add_to(cal_mon.days, cal_day))
+
+
+def cm_last_booked_daynum(cal_mon: CalendarMonth) -> int:
+    """
+    Return the number of the last day-of-the-month that actually has an
+    appointment in the given CalendarMonth, or 0 if there is no day
+    with an appointment in this CalendarMonth.
+    """
+    ensure_type(cal_mon, CalendarMonth)
+    if cm_is_empty(cal_mon):
+        return 0
+    else:
+        # This is an internal CalendarMonth function that can use the structure directly.
+        # We know that days are stored in order in cal_mon.days,
+        # so we can simply pick the last one.
+        last_cd = cal_mon.days[-1]
+        # But we are NOT allowed to use the internal structure of CalendarDay
+        # in this CalendarMonth-related function...
+        last_day = cd_day(last_cd)
+        return day_number(last_day)
+
+
+def cm_get_day(cal_mon: CalendarMonth, day: Day) -> CalendarDay:
+    """Return information about the given day of the given CalendarMonth."""
+    ensure_type(day, Day)
+    ensure_type(cal_mon, CalendarMonth)
+
+    # Can't iterate using cm_iter_days(), because that function calls *us*!
+    for cal_day in cal_mon.days:
+        if cd_day(cal_day) == day:
+            return cal_day
+
+    # The CalendarMonth doesn't contain a CalendarDay for the given day,
+    # so we create an empty one.
+    # Since we are programming in a functional way, callers are not allowed
+    # to modify the calendar month directly, so it doesn't matter that this
+    # CalendarDay isn't actually stored in the CalendarMonth.
+    return new_calendar_day(day)
+
+
+# ----- CalendarYear -----
+
+CalendarYear = NamedTuple("CalendarYear", [("months", List[CalendarMonth])])
+
+
+def new_calendar_year(months: List[CalendarMonth] = None) -> CalendarYear:
+    """
+    Create and return a new CalendarYear for the given month of the year,
+    with the given list of CalendarMonths.
+    """
+    if months is None:
+        # If we use [] as a default value above, then every call to this function will
+        # use the *same* list as a default value.  Instead we must use None as
+        # a value, and if something empty is provided, the line below correctly
+        # creates a *new* list each time.
+        months = []
+    else:
+        ensure_type(months, List[CalendarMonth])
+    return CalendarYear(months or [])
+
+
+def cy_iter_months(cal_year: CalendarYear):
+    """
+    To be used as `for cal_month in cm_iter_months(cal_year)`.  Iterates over all
+    12 months, not just those months that have appointments.
+    """
+    # For the purpose of TDDE24, you do not necessarily have to know exactly how this works.
+    # The point is that it gives others a way of iterating without knowing the internal
+    # data structures.
+    # If you are interested:  See "generator functions" at https://wiki.python.org/moin/Generators.
+    ensure_type(cal_year, CalendarYear)
+    for month in ALL_MONTHS:
+        yield cy_get_month(month, cal_year)
+
+
+def cy_is_empty(cal_year: CalendarYear) -> bool:
+    """Return true iff the given CalendarYear has no months."""
+    ensure_type(cal_year, CalendarYear)
+    return not cal_year.months
+
+
+def cy_plus_cm(cal_year: CalendarYear, cal_mon: CalendarMonth) -> CalendarYear:
+    """
+    Returns a copy of the given CalendarYear, where the given CalendarMonth
+    has been added in its proper position.  If the CalendarYear already
+    contains a CalendarMonth for the same Month, then the old CalendarMonth is
+    replaced with the new CalendarMonth.
+    """
+    ensure_type(cal_mon, CalendarMonth)
+    ensure_type(cal_year, CalendarYear)
+
+    month_to_insert = cm_month(cal_mon)
+
+    def add_to(months: List[CalendarMonth], month_to_add: CalendarMonth) -> List[CalendarMonth]:
+        if not months:
+            return [month_to_add]
+
+        next_month = months[0]
+        next_month_number = month_number(cm_month(next_month))
+
+        monthnum = month_number(cm_month(month_to_add))
+        if monthnum < next_month_number:
+            # Add the new CalendarMonth at the current position
+            return [month_to_add] + months
+        elif monthnum == next_month_number:
+            # Replace the CalendarMonth at the current position with the new CalendarMonth
+            return [month_to_add] + months[1:]
+        else:
+            # Need to move further
+            return [next_month] + add_to(months[1:], month_to_add)
+
+    if cm_is_empty(cal_mon):
+        return cal_year
+    elif cm_last_booked_daynum(cal_mon) > days_in_month(month_to_insert):
+        raise ValueError(f"Too few days in {month_name(month_to_insert)}.")
+    else:
+        return CalendarYear(add_to(cal_year.months, cal_mon))
+
+
+def cy_get_month(mon: Month, cal_year: CalendarYear) -> CalendarMonth:
+    """Return information about the given month of the given CalendarYear."""
+    ensure_type(mon, Month)
+    ensure_type(cal_year, CalendarYear)
+
+    # Can't iterate using cy_iter_months(), because that function calls *us*!
+    for cal_month in cal_year.months:
+        if cm_month(cal_month) == mon:
+            return cal_month
+
+    # The CalendarYear doesn't contain a CalendarMonth for the given month,
+    # so we create an empty one.
+    # Since we are programming in a functional way, callers are not allowed
+    # to modify the calendar month directly, so it doesn't matter that this
+    # CalendarMonth isn't actually stored in the CalendarYear.
+    return new_calendar_month(mon)
+
+
+# =========================================================================
+#  8. A few tests.
+# =========================================================================
+
+# ---- ensure_type: A few simple tests ----
+
+
+def test_ensure_type() -> None:
+    """
+    A few tests for ensure_type.
+    """
+    time1 = new_time_from_string("12:00")
+    time2 = new_time_from_string("12:30")
+    ensure_type(time1, Time)
+    ensure_type([time1, time2], List[Time])
+    ensure_type({"a": time1, "b": time2}, Dict[str, Time])
+    ensure_type({"a": {1:1}, "b": {2:2}}, Dict[str, Dict[int, int]])
+    ts = new_time_span(time1, time2)
+    ensure_type(ts, TimeSpan)
+    ensure_type([[ts]], List[List[TimeSpan]])
+    ensure_type(1, Any)
+    ensure_type([1, 2, "abc"], List[Any])
+
+    def ensure_fail(*args):
+        try:
+            ensure_type(*args)
+        except AssertionError:
+            return
+        assert False, f"ensure_type{args} should fail."
+
+    ensure_fail(1, str)
+    ensure_fail(1, List[Any])
+    ensure_fail([1], List[str])
+    ensure_fail([[[]], [1]], List[List[List[Any]]])
+    ensure_fail([1, 2, "str"], List[int])
+
+    print("ensure_type: All tests passed.")
+
+
+# ---- Time: A few simple tests ----
+
+
+def test_time() -> None:
+    """
+    A few simple tests for Time.
+    """
+    time1 = new_time_from_string("12:00")
+    time2 = new_time_from_string("12:30")
+    assert time_precedes(time1, time2)
+    assert time_equals(time1, time1)
+    assert time_precedes_or_equals(time1, time2)
+    assert time_precedes_or_equals(time1, time1)
+    assert not time_precedes_or_equals(time2, time1)
+    assert hour_number(time_hour(time1)) == 12
+    assert hour_number(time_hour(time2)) == 12
+    assert minute_number(time_minute(time1)) == 0
+    assert minute_number(time_minute(time2)) == 30
+
+    print("test_time: All tests passed.")
+
+
+# ---- TimeSpan: A few simple tests ----
+
+
+def test_timespan_duration() -> None:
+    """
+    A few simple tests for lab 8A.  These tests don't necessarily test *everything*, but
+    they help you see some of the problems with the current implementation of the
+    other functions in lab8a.py.
+    """
+    time1 = new_time(new_hour(10), new_minute(15))
+    time2 = new_time(new_hour(13), new_minute(30))
+    span1 = new_time_span(time1, time2)
+    span2 = new_time_span(new_time_from_string("12:10"),
+                          new_time_from_string("15:45"))
+
+    assert time_equals(ts_start(span1), time1)
+    assert time_equals(ts_end(span1), time2)
+
+    assert ts_equals(span1, span1)
+    assert ts_overlap(span1, span2)
+
+    overlap = ts_overlapping_part(span1, span2)
+
+    assert ts_equals(overlap, new_time_span(new_time(new_hour(12), new_minute(10)),
+                                            new_time(new_hour(13), new_minute(30))))
+
+    assert duration_equals(ts_duration(overlap), new_duration(new_hour(1), new_minute(20)))
+    assert duration_equals(ts_duration(span1), new_duration_from_string("3:15"))
+    assert duration_equals(ts_duration(span2), new_duration_from_string("3:35"))
+
+    print("test_timespan_duration: All tests passed.")
+
+
+if __name__ == '__main__':
+    test_ensure_type()
+    test_time()
+    test_timespan_duration()
diff --git a/lab8/cal_booking.py b/lab8/cal_booking.py
new file mode 100644
index 0000000000000000000000000000000000000000..016180c69aee5f3dcad2aa0ec591d8784c32c510
--- /dev/null
+++ b/lab8/cal_booking.py
@@ -0,0 +1,86 @@
+# =========================================================================
+#  The Calendar - Functional and imperative programming in Python
+#
+#  Modul: cal_booking.py
+#  Updated: 2004-07-30 av Peter Dalenius
+#    Translated to Python 2012 by Peter L-G
+#    Translated to English 2013 by Anders M.L.
+#    Changes in 2020 by Jonas K (NamedTuple, type hints, ...)
+#
+#  Dependencies:
+#    cal_abstraction.py
+# =========================================================================
+
+# This module contains lower-level calculations and functions for booking
+# appointments, finding unallocated time etc. Functions here never
+# operate directly on Python objects, but rather go through the primitives
+# and functions provided in cal_abstraction.py.
+
+
+from cal_abstraction import *
+
+
+# =========================================================================
+#  1. Properties of calendar contents.
+# =========================================================================
+
+
+def is_booked_during(cal_day: CalendarDay, ts: TimeSpan) -> bool:
+    """
+    Return true iff any appointment during the provided calendar day collides
+    (overlaps) with the proposed time span.
+    """
+    return cd_any_appointment_satisfies(
+        cal_day, lambda app: ts_overlap(ts, app_span(app))
+    )
+
+
+def is_booked_from(cal_day: CalendarDay, t: Time) -> bool:
+    """
+    Return true iff there is an appointment during the given day
+    that starts at exactly the specific time.
+    """
+    return cd_any_appointment_satisfies(
+        cal_day, lambda app: time_equals(t, ts_start(app_span(app)))
+    )
+
+
+# =========================================================================
+#  2. Making and removing appointments.
+# =========================================================================
+
+
+def plus_appointment(
+    cal_year: CalendarYear,
+    day: Day,
+    mon: Month,
+    start: Time,
+    end: Time,
+    subject: Subject,
+) -> CalendarYear:
+    """
+    Return a new CalendarYear where an appointment, specified by the parameters,
+    has been added in the appropriate CalendarDay.
+
+    This is a helper function using our internal data types.  See also the book()
+    function in cal_ui.py, which provides an external user interface to be called
+    by users.
+    """
+
+    # Adds a new appointment by taking apart a calendar year, inserting the
+    # appointment into the correct calendar day, and then putting it back together.
+
+    # This function does not modify any structures, but rather builds a new one.
+    # The new calendar year generated by plus_appointment should then be used
+    # by the caller to replace the old calendar year.
+
+    app = new_appointment(new_time_span(start, end), subject)
+
+    old_cal_month = cy_get_month(mon, cal_year)
+    old_cal_day = cm_get_day(old_cal_month, day)
+
+    new_cal_day = cd_plus_appointment(old_cal_day, app)
+    new_cal_month = cm_plus_cd(old_cal_month, new_cal_day)
+    new_cal_year = cy_plus_cm(cal_year, new_cal_month)
+
+    return new_cal_year
diff --git a/lab8/cal_output.py b/lab8/cal_output.py
new file mode 100644
index 0000000000000000000000000000000000000000..ebbad7289de87c4c09d9219e8dedb6dea021acfe
--- /dev/null
+++ b/lab8/cal_output.py
@@ -0,0 +1,98 @@
+# =========================================================================
+#  The Calendar - Functional and imperative programming in Python
+#
+#  Module: cal_output.py
+#  Updated: 2004-07-30 by Peter Dalenius
+#    Translated to Python in 2012 by Peter L-G
+#    Translated to English in 2013 by Anders M.L.
+#    Changes in 2020 by Jonas K (NamedTuple, type hints, ...)
+#  Dependencies:
+#    cal_abstraction.py
+# =========================================================================
+
+from cal_abstraction import *
+
+
+# =========================================================================
+#  1. Printing simple datatypes
+# =========================================================================
+def show_hour(h: Hour) -> None:
+    """Print the parameter in an appropriate way, with no line break."""
+    print(h, end="")
+
+
+def show_minute(m: Minute) -> None:
+    """Print the parameter in an appropriate way, with no line break."""
+    print(m, end="")
+
+
+def show_day(d: Day) -> None:
+    """Print the parameter in an appropriate way, with no line break."""
+    print(day_number(d), end="")
+
+
+def show_month(m: Month) -> None:
+    """Print the parameter in an appropriate way, with no line break."""
+    print(month_name(m), end="")
+
+
+def show_subject(s: Subject) -> None:
+    """Print the parameter in an appropriate way, with no line break."""
+    print(subject_text(s), end="")
+
+
+# =========================================================================
+#  2. Printing compound datatypes
+# =========================================================================
+def show_duration(tr: Duration) -> None:
+    """Print the parameter in an appropriate way, with no line break."""
+    print(
+        f"{duration_hour(tr)} hours, "
+        f"{duration_minute(tr)} minutes"
+    )
+
+
+def show_time(t: Time) -> None:
+    """Print the parameter in an appropriate way, with no line break."""
+    # ...:02 prints two digits, beginning with 0 if the number is less than 10.
+    print(
+        f"{hour_number(time_hour(t)):02}:{minute_number(time_minute(t)):02}", end="",
+    )
+
+
+def show_ts(ts: TimeSpan) -> None:
+    """Print the parameter in an appropriate way, with no line break."""
+    show_time(ts_start(ts))
+    print("-", end="")
+    show_time(ts_end(ts))
+
+
+def show_date(d: Date) -> None:
+    """Print the parameter in an appropriate way, with no line break."""
+    show_day(date_day(d))
+    print(" ", end="")
+    show_month(date_month(d))
+
+
+def show_appointment(app: Appointment) -> None:
+    """Print the parameter in an appropriate way, with no line break."""
+    show_ts(app_span(app))
+    print(" ", end="")
+    show_subject(app_subject(app))
+
+
+def show_cd(cal_day: CalendarDay) -> None:
+    """Print the given calendar day, one appointment per line."""
+    for appointment in cd_iter_appointments(cal_day):
+        show_appointment(appointment)
+        print()
+
+
+# =========================================================================
+#  3. Miscellaneous output functions
+# =========================================================================
+def show_day_heading(d: Day, m: Month) -> None:
+    """Print an appropriate heading for the given day and month."""
+    s = f"{day_number(d)} {month_name(m)}"
+    print(s)
+    print("=" * len(s))
diff --git a/lab8/cal_ui.py b/lab8/cal_ui.py
new file mode 100644
index 0000000000000000000000000000000000000000..03287de36ea27cc1d0f4f46990e869fb24714611
--- /dev/null
+++ b/lab8/cal_ui.py
@@ -0,0 +1,223 @@
+# =========================================================================
+#  The Calendar - Functional and imperative programming in Python
+#
+#  Module: cal_ui.py
+#  Updated: 2004-11-10 by Peter Dalenius
+#    Translated to Python in 2012 by Peter L-G
+#    Translated to English in 2013 by Anders M.L.
+#    Changes in 2020 by Jonas K (NamedTuple, type hints, ...)
+#  Dependencies:
+#    cal_abstraction.py
+#    cal_booking.py
+#    cal_output.py
+# =========================================================================
+
+# This module ties the calendar together. It contains the functions
+# that users interact with, and the global dictionary where all
+# calendars are stored.
+
+# Functions in this file never delve deeper into the representation of the
+# calendar objects, or the internal logic of actually booking an appointment.
+# Such code is available in separate modules. These are imported automatically
+# upon the import of the calendar module.
+
+# The files have the following dependencies:
+
+#
+#               cal_ui.py
+#                   |
+#           +_______+________+
+#           |                |
+#      cal_booking.py   cal_output.py
+#           |                |
+#           +_______+________+
+#                   |
+#              cal_abstraction.py
+
+from cal_booking import *
+from cal_output import *
+
+import pickle
+
+# =========================================================================
+#  1. Storing and fetching calendars
+# =========================================================================
+
+# Right now a CalendarSet is a plain dict.
+CalendarSet = dict
+
+# Global variables are not nice... but in this case we are using the Python
+# prompt as the user interface, so we don't have a continuously running
+# program that can store the calendars somewhere non-global.
+calendars = CalendarSet()
+
+
+def get_calendar(cal_name: str) -> CalendarYear:
+    """Retrieve the calendar (year) with the given name.
+    The calendar must already exist."""
+    if calendar_exists(cal_name):
+        return calendars[cal_name]
+    else:
+        message = f"There is no calendar by the name {cal_name}."
+        raise ValueError(message)
+
+
+def insert_calendar(cal_name: str, cal_year: CalendarYear) -> None:
+    """Store the given calendar (year) under the given name."""
+    ensure_type(cal_name, str)
+    ensure_type(cal_year, CalendarYear)
+    calendars[cal_name] = cal_year
+
+
+def calendar_exists(cal_name: str) -> bool:
+    """Return true iff there exists a calendar with the given name."""
+    ensure_type(cal_name, str)
+    return cal_name in calendars
+
+
+def new_calendar(cal_name: str) -> None:
+    """
+    Create a new calendar (year) with the given name, without checking
+    if such a calendar already exists.
+    """
+    ensure_type(cal_name, str)
+    calendars[cal_name] = new_calendar_year()
+
+
+def save_calendars(filename: str) -> None:
+    """
+    Saves all calendars to file. The data is wrapped in [*CALFILE3000*, ...]
+    which is used as a tag for identifying calendar files.
+    """
+    with open(filename, "wb") as output:
+        pickle.dump(["*CALFILE3000*", calendars], output)
+
+
+def load_calendars(filename: str) -> bool:
+    """
+    Loads a set of calendars from the file with the given name.
+
+    If the file does not exist, cannot be read, or cannot be unpickled,
+    an exception is raised.
+
+    If the file can be read and contains correctly pickled data, but does not
+    have the proper high level structure, False is returned.
+
+    Otherwise, True is returned.
+
+    The file to be loaded is assumed to be non-hostile (cf the warning in the
+    Pickle module documentation http://docs.python.org/3/library/pickle.html).
+    """
+    with open(filename, "rb") as pkl_file:
+        content = pickle.load(pkl_file)
+
+    if (
+        isinstance(content, list)
+        and len(content) == 2
+        and content[0] == "*CALFILE3000*"
+    ):
+        global calendars
+        calendars = content[1]
+        return True
+    else:
+        return False
+
+
+# =========================================================================
+#  2. User interface
+# =========================================================================
+
+
+def create(cal_name: str) -> None:
+    """
+    Create a calendar (year) with the given name.  The calendar must not
+    already exist.
+    """
+    ensure_type(cal_name, str)
+    if calendar_exists(cal_name):
+        print(f"A calendar by the name {cal_name} already exists.")
+    else:
+        new_calendar(cal_name)
+        print(f"A new calendar by the name {cal_name} has been created.")
+
+
+def show_calendars() -> None:
+    """Show the names of all calendars that have been created."""
+    if calendars:
+        print("The following calendars exist:")
+        for cal_name in calendars:
+            print(cal_name)
+    else:
+        print("No calendars have been created.")
+
+
+def book(cal_name: str, d: int, m: str, t1: str, t2: str, subject_txt: str) -> None:
+    """Book a new appointment in the calendar with the given name."""
+    day = new_day(d)
+    mon = new_month(m)
+    start = new_time_from_string(t1)
+    end = new_time_from_string(t2)
+    subject = new_subject(subject_txt)
+    cal_year = get_calendar(cal_name)
+    cal_month = cy_get_month(mon, cal_year)
+    cal_day = cm_get_day(cal_month, day)
+
+    # Ensure that the date is proper.  If not, this will raise an exception.
+    new_date(day, mon)
+
+    if time_precedes(end, start):
+        print("Invalid appointment time (wrong order of start and finish).")
+    elif is_booked_during(cal_day, new_time_span(start, end)):
+        print("The proposed time is already taken.")
+    else:
+        # Get a new CalendarYear with the new appointment...
+        new_year = plus_appointment(cal_year, day, mon, start, end, subject)
+        # ...and let the calendar name refer to this CalendarYear instead.
+        insert_calendar(cal_name, new_year)
+        print("The appointment has been booked.")
+
+
+def show(cal_name: str, d: int, m: str) -> None:
+    """Show all appointments in the calendar with the given name,
+    during the given date (day and month)."""
+    day = new_day(d)
+    mon = new_month(m)
+    cal_day = cm_get_day(cy_get_month(mon, get_calendar(cal_name)), day)
+
+    new_date(day, mon)  # Ensure that the date is proper
+
+    if cd_is_empty(cal_day):
+        print("No appointments this day.\n")
+    else:
+        show_day_heading(day, mon)
+        show_cd(cal_day)
+
+
+def save(filename: str) -> None:
+    """Save the current set of calendars to the file with the given name."""
+    save_calendars(filename)
+    print(f"The calendars have been saved to {filename}.")
+
+
+def load(filename: str) -> None:
+    """Load a set of calendars from the file with the given name."""
+    try:
+        if load_calendars(filename):
+            print("New calendars have been loaded.")
+        else:
+            print("The file does not exist, or it is devoid of saved calendars.")
+    except IOError as e:
+        print(f"Error opening or reading calendar file {filename}")
+        print(e)
+
+
+def calhelp() -> None:
+    """Show help for the calendar system."""
+    print("The Calendar. \n\n")
+    print("-" * 50)
+    print("A quick reminder of your options:")
+    print("  create(name)")
+    print("  book(name, day, month, start, end, subject)")
+    print("  show(name, day, month)")
+    print("  save(filename)")
+    print("  load(filename)")
diff --git a/lab8/lab8a.py b/lab8/lab8a.py
new file mode 100644
index 0000000000000000000000000000000000000000..e357b838863524d643747c22937f073f5639da97
--- /dev/null
+++ b/lab8/lab8a.py
@@ -0,0 +1,101 @@
+# This code violates abstraction layers, and should be reimplemented in lab 8A.
+from cal_abstraction import *
+
+
+def ts_equals(ts1: TimeSpan, ts2: TimeSpan):
+    """Return true iff the two given TimeSpans are equal."""
+    ensure_type(ts1, TimeSpan)
+    ensure_type(ts2, TimeSpan)
+
+    #ts1 (TimeSpan): The first TimeSpan to compare.
+    #ts2 (TimeSpan): The second TimeSpan to compare.
+    #Id ts1 < then ts2 return true
+    return (time_equals(ts_start(ts1), ts_start(ts2)) and
+            time_equals(ts_end(ts1), ts_end(ts2)))
+
+
+def ts_overlap(ts1: TimeSpan, ts2: TimeSpan) -> bool:
+    """Return true iff the two given TimeSpans overlap."""
+    ensure_type(ts1, TimeSpan)
+    ensure_type(ts2, TimeSpan)
+
+    # True if ts1 and ts2 overlap, False otherwise.
+    return (
+            # TS1 isn't strictly after TS2
+            time_precedes(ts_start(ts1), ts_end(ts2)) and
+            # TS2 isn't strictly after ts1
+            time_precedes(ts_start(ts2), ts_end(ts1))
+    )
+
+
+def ts_overlapping_part(ts1: TimeSpan, ts2: TimeSpan) -> TimeSpan:
+    """Return the overlapping part of two overlapping time spans,
+    under the assumption that they really *are* overlapping."""
+    ensure_type(ts1, TimeSpan)
+    ensure_type(ts2, TimeSpan)
+    ensure((ts1, ts2), lambda tup: ts_overlap(tup[0], tup[1]))
+
+    
+    #The TimeSpan representing the overlapping part of ts1 and ts2.
+    if time_precedes(ts_start(ts1), ts_end(ts2)):
+        if time_precedes(ts_start(ts2), ts_end(ts1)):
+            return new_time_span(ts_start(ts2),ts_end(ts1))
+    else:
+        return new_time_span(ts_start(ts1),ts_end(ts2))
+        
+
+def ts_duration(ts: TimeSpan) -> "Duration":
+    """Return the duration (length) of a TimeSpan"""
+    ensure_type(ts, TimeSpan)
+
+    #ts (TimeSpan): The TimeSpan to calculate the duration of
+    #The duration of the TimeSpan.
+    mins = (
+        hour_number(time_hour(ts_end(ts))) * 60 + minute_number(time_minute(ts_end(ts))) - hour_number(time_hour(ts_start(ts))) * 60 - minute_number(time_minute(ts_start(ts)))
+           
+    )
+    return new_duration(Hour(mins // 60), Minute(mins % 60))
+
+
+def duration_is_longer_or_equal(d1: Duration, d2: Duration):
+    """
+    Return true iff the first duration is longer than, or equally as long as,
+    the second duration.
+    """
+    ensure_type(d1, Duration)
+    ensure_type(d2, Duration)
+    
+    # True if the first Duration is longer than, or equally as long as,the second Duration. False otherwise.
+    hours1 = duration_hour(d1)
+    hours2 = duration_hour(d2)
+    mins1 = duration_minute(d1)
+    mins2 = duration_minute(d2)
+
+   
+
+    return (hours1, mins1) >= (hours2, mins2)
+
+
+def duration_equals(d1: Duration, d2: Duration):
+    """
+    Return true iff the first duration is equally as long as,
+    the second duration.
+    """
+
+    # True if the first duration is equally as long as the second duration, False otherwise.
+    ensure_type(d1, Duration)
+    ensure_type(d2, Duration)
+    
+    hours1 = duration_hour(d1)
+    hours2 = duration_hour(d2)
+    mins1 = duration_minute(d1)
+    mins2 = duration_minute(d2)
+
+   
+
+    return (hours1, mins1) == (hours2, mins2)
+
+
+if __name__ == "__main__":
+    test_timespan_duration()
+
diff --git a/lab8/lab8b.py b/lab8/lab8b.py
new file mode 100644
index 0000000000000000000000000000000000000000..1aaf7e0e675abcfd3f5aeafa47fb3fe5264fda8c
--- /dev/null
+++ b/lab8/lab8b.py
@@ -0,0 +1,94 @@
+# =========================================================================
+# Type definition
+# =========================================================================
+from cal_abstraction import *
+from cal_output import *
+
+# Define the type somehow...  The initial "" is simply here as a placeholder.
+
+TimeSpanSeq = NamedTuple("TimeSpanSeq", [("Seq", List[TimeSpan])])
+#Seq=NamedTuple("Seq",[("TimeSpan",List[])])
+
+
+# =========================================================================
+#  Function implementations
+# =========================================================================
+
+# Implement these functions!  Also determine if you need *additional* functions.
+
+
+def new_time_span_seq(Seq: List[TimeSpan] = None) -> TimeSpanSeq:
+        """Create and return a new TimeSpanSeq with the given TimeSpans."""
+
+        #If there's no seq then create one
+        #then return a new TimeSpanSeq with the given TimeSpans.
+        if Seq is None:
+            Seq = []
+        
+        return TimeSpanSeq(Seq)
+
+def tss_is_empty(tss:TimeSpanSeq) -> bool:
+    """Checks if the given TimeSpanSeq is empty."""
+    ensure_type(tss, TimeSpanSeq)
+    return not tss
+    
+
+
+def tss_plus_span(tss: TimeSpanSeq, ts: TimeSpan) -> TimeSpanSeq:
+    """Adds the given time span to the given TimeSpanSeq."""
+    ensure_type(tss, TimeSpanSeq)
+    ensure_type(ts, TimeSpan)
+
+    def add_TimeSpan(timespan: TimeSpan, seq: List[TimeSpan]):
+        #Test if seq is empty, if so create one
+        if not seq:
+            return [timespan]
+
+        #Test if the beginning of the given timspan is < then the beginning of timespans in seq 
+        #If so add timespan to seq
+        elif time_precedes(ts_start(timespan), ts_start(seq[0])):
+            return [timespan] + seq
+
+        else:
+            return [seq[0]] + add_TimeSpan(timespan, seq[1:])
+
+    return new_time_span_seq(add_TimeSpan(ts, tss.Seq))
+
+
+
+def tss_iter_spans(tss: TimeSpanSeq):
+    """Iterates over all time spans in the given TimeSpanSeq."""
+    ensure_type(tss, TimeSpanSeq)
+    for timespan in tss[0]:
+        yield timespan
+
+def show_time_spans(tss: TimeSpanSeq):
+    """ Prints out all time spans in the given TimeSpanSeq."""
+    #for-loop over timespans in seq
+    for timespan in tss[0]:
+        show = show_ts(timespan)
+        print(' ')
+    return show 
+
+# Keep only time spans that satisfy pred.
+# You do not need to modify this function.
+def tss_keep_spans(tss, pred):
+    """  Creates a new TimeSpanSeq containing only the time spans that satisfy the given predicate."""
+    result = new_time_span_seq()
+
+    #for-loop over timespans in seq using tss_iter_spans
+    for span in tss_iter_spans(tss):
+        if pred(span):
+            result = tss_plus_span(result, span)
+
+    return result
+
+
+# We don't provide a way of retrieving the list of appointments.
+# Instead, we provide a way of *iterating* over all appointments.
+
+
+if __name__ == "__main__":
+    test_timespan_duration()
+
+
diff --git a/lab8/lab8c.py b/lab8/lab8c.py
new file mode 100644
index 0000000000000000000000000000000000000000..08984e197cd9cef240e6acff9d8bee4fc78b8c12
--- /dev/null
+++ b/lab8/lab8c.py
@@ -0,0 +1,82 @@
+# Write your code for lab 8C (remove) here.
+from cal_ui import *
+def remove(cal_name: str, d: int, m: str, t1: str):
+    """Removes an appointment from the calendar."""
+
+    # Convert input values to appropriate types
+    ensure_type(cal_name, str)
+    day = new_day(d)
+    mon = new_month(m)
+    start = new_time_from_string(t1)
+    new_app = []
+    
+
+    # Check if the calendar exists
+    if calendar_exists(cal_name):
+        cal_year = get_calendar(cal_name)
+        cal_month = cy_get_month(mon, cal_year)
+        cal_day = cm_get_day(cal_month, day)
+        if cal_year and cal_month and cal_day:
+            
+            #If appointments exist, then do a for loop over them
+            for appointments in cal_day[1:]:
+                # If no appointments exist for the given date
+                #print this there is no appontment with the given date
+
+                if not appointments:
+                    print(f'there is no appontment with the given date')
+                    
+                else:
+                    for cal_app in appointments:
+                        # If the start time of the appointment does not match the input value,
+                        # add the appointment to the new_app list
+                        if ts_start(app_span(cal_app)) != start:
+                            new_app.append(cal_app)
+                            
+                            
+                    for cal_app in appointments:
+                        # If the start time of the appointment matches the input value,
+                        # create a new calendar and insert the updated appointments
+                        if ts_start(app_span(cal_app)) == start:
+                            
+                            ny_cal(cal_name,day,mon,start,new_app,d,m,t1)               
+    
+    else:
+        print(f'There is no calendar by the cal_name.')
+    
+        
+      
+                  
+def ny_cal(cal_name,day,mon,start,new_app,d,m,t1):
+    """Creates a new calendar and inserts updated appointments."""
+
+
+    cal_year = CalendarYear([])
+    insert_calendar(cal_name, cal_year)
+    
+    #for-loop over appointments in nuw_app
+    for app in new_app:
+        ny_sub = str(subject_text(app_subject(app)))
+        
+        
+        #  Convert updated appointment values to appropriate types
+        #And then creates a new calendar
+        ny_ts = app_span(app)
+        ny_t1 = ts_start(ny_ts)
+        ny_t2 = ts_end(ny_ts)
+        t1 = f"{hour_number(time_hour(ny_t1)):02}:{minute_number(time_minute(ny_t1)):02}"
+        t2 = f"{hour_number(time_hour(ny_t2)):02}:{minute_number(time_minute(ny_t2)):02}"
+        day = new_day(d)
+        mon = new_month(m)
+        start = new_time_from_string(t1)
+        end = new_time_from_string(t2)
+        subject = new_subject(ny_sub)
+        cal_year = get_calendar(cal_name)
+        cal_month = cy_get_month(mon, cal_year)
+        cal_day = cm_get_day(cal_month, day)
+        new_year = plus_appointment(cal_year, day, mon, start, end, subject)
+        insert_calendar(cal_name, new_year)
+        
+    
+    
+    print(f'Appointment removed.')
diff --git a/lab8/lab8d.py b/lab8/lab8d.py
new file mode 100644
index 0000000000000000000000000000000000000000..74cd381f8bd9c63e5eac3872231eb4e7c86ac4de
--- /dev/null
+++ b/lab8/lab8d.py
@@ -0,0 +1,88 @@
+# Write your code for lab 8d here.
+from cal_abstraction import CalendarDay, Time
+from settings import CHECK_AGAINST_FACIT
+from cal_ui import *
+
+if CHECK_AGAINST_FACIT:
+    try:
+        from facit_la8_uppg import TimeSpanSeq
+    except:
+        print("*" * 100)
+        print("*" * 100)
+        print("Kan inte hitta facit; ändra CHECK_AGAINST_FACIT i test_driver.py till False")
+        print("*" * 100)
+        print("*" * 100)
+        raise
+else:
+    from lab8b import *
+
+
+def free_spans(cal_day: CalendarDay, start: Time, end: Time) -> TimeSpanSeq:
+    """Given a CalendarDay object, start Time and end Time, returns a TimeSpanSeq object representing the free time spans within the given time interval of the day."""
+    busy_times = []
+
+    #for-loop through each appointment on the day
+    for app in cal_day[1:]:
+        # Loop through each time span of the appointment
+        for time_span in app:
+            # If the appointment overlaps with the given time interval, add it to the busy_times list
+            if time_precedes(ts_start(app_span(time_span)), end) and time_precedes(start, ts_end(app_span(time_span))):
+                busy_times.append(new_time_span(ts_start(app_span(time_span)), ts_end(app_span(time_span))))
+
+    free_spans = []
+    # If there are no busy times, the entire time interval is free
+    if len(busy_times) == 0:
+        free_spans.append(new_time_span(start, end))
+    else:
+        #Loop through each busy time span
+        for i in range(len(busy_times)):
+            #If it is the first busy time span and it starts after the start of the time interval,
+            # add a free time span
+            if i == 0 and start <  ts_start(busy_times[i]):
+                free_spans.append(new_time_span(start, ts_start(busy_times[i])))
+
+            #If it is not the first busy time span and there is a gap between the previous and current time spans,
+            #add a free time span
+            elif i > 0 and ts_end(busy_times[i-1]) < ts_start(busy_times[i]):
+                free_spans.append(new_time_span(ts_end(busy_times[i-1]), ts_start(busy_times[i])))
+
+            # If it is the last busy time span and it ends before the end of the time interval,
+            #add a free time span
+            if i == len(busy_times)-1 and ts_end(busy_times[i]) < end:
+                free_spans.append(new_time_span(ts_end(busy_times[i]), end))
+
+    return TimeSpanSeq(free_spans)
+
+
+
+def show_free(cal_name: str, day: int, month: str, start: str, end: str) -> None:
+    """Given a calendar name, day, month, start time and end time, prints out the free time spans within the given time interval of the day."""
+    
+    # Convert start and end strings to Time objects
+    start_time = new_time_from_string(start)
+    end_time = new_time_from_string(end)
+
+
+    #Get the calendar year, month, and day
+    cal_year = get_calendar(cal_name)
+    day = new_day(day)
+    mon = new_month(month)
+    cal_month = cy_get_month(mon, cal_year)
+    cal_day = cm_get_day(cal_month, day)
+    
+    #Get the list of free time slots
+    free_spans_list = free_spans(cal_day, new_time_from_string(start), new_time_from_string(end))
+    
+        
+    
+    # If there isn't a free time slot, print a message to notify the user
+    if not free_spans_list[0]:
+        print("There isn't a free time")
+    else:
+        
+        #Otherwise, print the free time slots
+        for free_span in free_spans_list[0]:
+
+            print(f"{hour_number(time_hour(ts_start(free_span))):02}:{minute_number(time_minute(ts_start(free_span))):02}"\
+                  ,"-",f"{hour_number(time_hour(ts_end(free_span))):02}:{minute_number(time_minute(ts_end(free_span))):02}")
+    
diff --git a/lab8/lab8d_tests.py b/lab8/lab8d_tests.py
new file mode 100644
index 0000000000000000000000000000000000000000..33f8e04a1e93ca8780a2b57247852dad7cb411a9
--- /dev/null
+++ b/lab8/lab8d_tests.py
@@ -0,0 +1,80 @@
+
+# Write your code for lab 8d here.
+
+from test_driver import store_test_case, run_free_spans_tests
+
+
+# Create additional test cases, and add to them to create_tests_for_free_span().
+
+def create_tests_for_free_span() -> dict:
+    """Create and return a number of test cases for the free_spans function"""
+    test_cases = dict()
+
+    store_test_case(
+        test_cases,
+        1,
+        start_str="08:00",  # Search interval starts
+        end_str="21:00",  # Search interval ends
+        booking_data=["07:00-09:00", "13:00-18:00"],  # This day's appointments
+        exp_result=["09:00-13:00", "18:00-21:00"],
+    )  # Expected free time
+
+
+
+
+
+    #I testfall 2 har vi flera bokade tider spridda över hela dagen,
+    #för att testa om free_spans()-funktionen hanterar flera tidsluckor korrekt.
+    
+    store_test_case(
+    test_cases,
+    2,
+    start_str="08:00",  
+    end_str="21:00",  
+    booking_data=["09:00-10:00", "11:00-12:00", "13:00-14:00", "15:00-16:00"],  
+    exp_result=["08:00-09:00","10:00-11:00", "12:00-13:00", "14:00-15:00", "16:00-21:00"],
+    )
+    
+    
+    #I testfall 3 har vi en bokad tid som precis täcker det sökta intervallet,
+    #för att testa om free_spans()-funktionen returnerar en tom lista i det fallet.
+    
+    store_test_case(
+    test_cases,
+    3,
+    start_str="09:00",  
+    end_str="10:00", 
+    booking_data=["09:00-10:00"],  
+    exp_result=[],
+    )  
+
+    
+    #I testfall 4 har vi inga bokade tider alls, för att testa om free_spans()-funktionen hanterar det här
+    #fallet korrekt och returnerar en tidslucka för hela det sökta intervallet.
+
+    store_test_case(
+    test_cases,
+    4,
+    start_str="08:00", 
+    end_str="21:00", 
+    booking_data=[],  
+    exp_result=["08:00-21:00"],
+    ) 
+    
+    
+    
+    
+    
+    # -------- YOUR TEST CASES GO HERE -----------------------
+    # For each case, add a brief description of what you want to test.
+
+    print("Test cases generated.")
+
+    return test_cases
+
+
+if __name__ == '__main__':
+    # Actually run the tests, using the test driver functions
+    tests = create_tests_for_free_span()
+    run_free_spans_tests(tests)
+
diff --git a/lab8/lab8e.py b/lab8/lab8e.py
new file mode 100644
index 0000000000000000000000000000000000000000..16078a683bf38a01b3d0592c9b14017406abef9a
--- /dev/null
+++ b/lab8/lab8e.py
@@ -0,0 +1 @@
+# Placeholder for the optional lab 8E.
diff --git a/lab8/settings.py b/lab8/settings.py
new file mode 100644
index 0000000000000000000000000000000000000000..9f418fe66abb6f96f47d80c4277bef58ecb53382
--- /dev/null
+++ b/lab8/settings.py
@@ -0,0 +1,7 @@
+USE_DEFAULT_DURATION_TYPE = True
+USE_DEFAULT_TIMESPAN_TYPE = True
+
+# Used by the lab developers to test that the tests work for our
+# reference implementation.  If we have left this set to True,
+# the import below will crash; in that case, change it to False.
+CHECK_AGAINST_FACIT = False
diff --git a/lab8/test_driver.py b/lab8/test_driver.py
new file mode 100644
index 0000000000000000000000000000000000000000..fc7baaab1a1f8143ca56b74cb2f4215ca0dbfacf
--- /dev/null
+++ b/lab8/test_driver.py
@@ -0,0 +1,202 @@
+# =========================================================================
+#  Calendar - Functional and imperative programming in Python
+#
+#  Module: test_driver.py
+#  Created: 2008-10-07 by Peter Dalenius
+#  Updated 2009-09-25 by Anders Haraldsson
+#    Translated to Python in 2012 by Peter L-G
+#    Translated to English in 2013 by Anders M.L.
+#    Changes in 2020 by Jonas K (NamedTuple, type hints, ...)
+#  Dependencies:
+#    cal_abstraction.py
+#    cal_booking.py
+#    cal_output.py
+# =========================================================================
+from typing import Dict
+
+# THIS FILE SHOULD NOT BE MODIFIED!
+# Your own test cases are specified in lab8d.py, which *uses*
+# the test functionality defined in this file.
+
+
+from cal_ui import *
+from settings import CHECK_AGAINST_FACIT
+
+if CHECK_AGAINST_FACIT:
+    try:
+        from facit_la8_uppg import *
+    except:
+        print("*" * 100)
+        print("*" * 100)
+        print("Kan inte hitta facit; ändra CHECK_AGAINST_FACIT i test_driver.py till False")
+        print("*" * 100)
+        print("*" * 100)
+        raise
+else:
+    from lab8b import *
+    from lab8d import free_spans
+
+# In the code below, we assume that the function that generates the set of
+# free time spans (a time_spans object) is called free_spans. It is also
+# assumed to be invoked by free_spans(cal_day, ts_start, ts_end), in that
+# order. You may need to modify your code to conform to this standard.
+
+
+# =========================================================================
+#  1. Additional components
+# =========================================================================
+
+# The goal is to see if the free_spans function produces correct results.
+# In order to simplify the specification of test cases, we provide a set of
+# functions that convert human-readable descriptions of appointments and
+# search spans during a day, into testable data (actual CalendarDay and
+# TimeSpanSeq objects, for example).  We also provide some functions that
+# verify that data has been created correctly.
+
+
+def test_tss_order(tss: TimeSpanSeq) -> None:
+    """
+    Verify that the given TimeSpanSeq contains spans in temporal order,
+    and signal an error otherwise
+    """
+    # At this point we don't have a complete test suite for the construction
+    # and insertion of TimeSpan objects in a TimeSpanSeq.  Instead we *assume*
+    # that TimeSpanSeq works properly, and if not, we signal an error
+    # through an assertion.  Had this been a full test system for the
+    # entire calendar, we should instead have had separate test cases for
+    # TimeSpanSeq, with the ability to generate a list of test cases that pass
+    # and don't pass.
+
+    span_seq = [span for span in tss_iter_spans(tss)]
+    if not span_seq:
+        return
+
+    # Generate pairs of (span, next span)
+    span_pairs = zip(span_seq, span_seq[1:])
+    for ts1, ts2 in span_pairs:
+        assert time_precedes_or_equals(ts_start(ts1), ts_start(ts2)), (
+            f"Error in TimeSpanSeq:  Spans are not inserted in correct temporal order: "
+            f"{ts1} is before {ts2} in {span_seq}"
+        )
+
+
+# This function converts from the human-readable form ["13:15-15:00", ...] to
+# corresponding TimeSpan(Seq) objects.
+def test_strings_to_spans(seq: List[str]) -> TimeSpanSeq:
+    tss = new_time_span_seq()
+    for item in seq:
+        times = item.split("-")
+        start = new_time_from_string(times[0])
+        end = new_time_from_string(times[1])
+        span = new_time_span(start, end)
+        tss = tss_plus_span(tss, span)
+
+    # To be on the safe side: Are the inserted spans there, in the right order?
+    test_tss_order(tss)
+    span_seq = [span for span in tss_iter_spans(tss)]
+    for item in seq:
+        times = item.split("-")
+        start = new_time_from_string(times[0])
+        end = new_time_from_string(times[1])
+        span = new_time_span(start, end)
+        assert span in span_seq
+    return tss
+
+
+def test_time_spans_to_cd(day: Day, tss: TimeSpanSeq) -> CalendarDay:
+    """
+    Generate a CalendarDay with one test appointment for each TimeSpan in the
+    given TimeSpanSeq.
+    """
+    ensure_type(tss, TimeSpanSeq)
+
+    cd = new_calendar_day(day)
+    for ts in tss_iter_spans(tss):  # type: TimeSpan
+        cd = cd_plus_appointment(cd, new_appointment(ts, new_subject("Test")))
+
+    return cd
+
+
+def test_tss_equals(tss1: TimeSpanSeq, tss2: TimeSpanSeq) -> bool:
+    """
+    Returns true iff tss1 and tss2 contains the same time spans in the same order.
+    """
+    ensure_type(tss1, TimeSpanSeq)
+    ensure_type(tss2, TimeSpanSeq)
+
+    spans1 = [span for span in tss_iter_spans(tss1)]
+    spans2 = [span for span in tss_iter_spans(tss2)]
+
+    if len(spans1) != len(spans2):
+        return False
+
+    for index in range(len(spans1)):
+        if not ts_equals(spans1[index], spans2[index]):
+            return False
+
+    return True
+
+
+# =========================================================================
+#  2. Building and using test cases
+# =========================================================================
+
+
+def store_test_case(
+        test_cases: Dict[int, List],
+        test_nr: int,
+        start_str: str,
+        end_str: str,
+        booking_data: List[str],
+        exp_result: List[str],
+) -> None:
+    """
+    This function stores information about a single test case that the test case runner
+    can execute.
+
+    :param test_cases: The test case mapping where the test case is stored.
+    :param test_nr:  The test number; this should uniquely identify the test case.
+    :param start_str: The start of the search interval.
+    :param end_str:  The end of the search interval.
+    :param booking_data:  A list of appointment spans in text format (in the form "HH:MM-HH:MM")
+    :param exp_result: A similar list of expected results
+    """
+    start = new_time_from_string(start_str)
+    end = new_time_from_string(end_str)
+    cal_day = test_time_spans_to_cd(new_day(1), test_strings_to_spans(booking_data))
+    res_mts = test_strings_to_spans(exp_result)
+
+    # The data generated is a test case (with a calendar day and a set of
+    # expected time spans) that is added to the set of test cases.
+    test_cases[test_nr] = [start, end, cal_day, res_mts]
+
+
+def run_free_spans_tests(test_cases: dict):
+    """
+    Goes through the specified test cases and compares actual and expected output.
+    """
+    all_ok = True
+    cases = 0
+    for test_nr in test_cases:
+        cases += 1
+        current_case = test_cases[test_nr]
+        start = current_case[0]
+        end = current_case[1]
+        cal_day = current_case[2]
+        expected_results = current_case[3]
+
+        if not test_tss_equals(free_spans(cal_day, start, end), expected_results):
+            all_ok = False
+
+            print("----")
+            print(f"Test case {test_nr} generates unexpected output.")
+            print("Free time spans:")
+            show_time_spans(free_spans(cal_day, start, end))
+            print()
+            print("Expected:")
+            show_time_spans(expected_results)
+            print("----")
+            print()
+
+    if all_ok:
+        print("All ({}) test cases OK.".format(cases))