Disabled external gits

This commit is contained in:
2022-04-07 18:46:57 +02:00
parent 88cb3426ad
commit 15e7120d6d
5316 changed files with 4563444 additions and 6 deletions

View File

@@ -0,0 +1,4 @@
scalaVersion := "3.0.2"
libraryDependencies += "org.scala-lang.modules" %% "scala-parser-combinators" % "2.0.0"

Binary file not shown.

View File

@@ -0,0 +1 @@
sbt.version=1.5.5

View File

@@ -0,0 +1,131 @@
package fos
object Infer {
case class TypeScheme(params: List[TypeVar], tp: Type)
type Env = List[(String, TypeScheme)]
type Constraint = (Type, Type)
case class TypeError(msg: String) extends Exception(msg)
extension (t: Type)
def in(s: Type): Boolean = getTV(s).contains(t)
extension (e: Env)
def has(t: TypeVar): Boolean = e.find(_._2.params.contains(t)).nonEmpty
private var ftv_count = -1
def freshTV(): TypeVar = {
ftv_count += 1
TypeVar("v"+ftv_count.toString)
}
def getTV(t: Type): List[TypeVar] = getTV(t, Nil)
def getTV(t: Type, ex: List[TypeVar]): List[TypeVar] = t match {
case tpe@TypeVar(_) if !(ex.contains(tpe))=> List(tpe)
case FunType(tpe1, tpe2) => getTV(tpe1,ex) ::: getTV(tpe2,ex)
case _ => Nil
}
def generalizeTV(tp: Type, env: Env) : TypeScheme = TypeScheme(getTV(tp).filter(env.has), tp)
def collectHelper(env: Env, t: Term, tpe: Type): (Type, List[Constraint]) =
collectHelper(env, t, tpe, tpe)
def collectHelper(env: Env, t: Term, tpe1: Type, tpe2: Type): (Type, List[Constraint]) =
val (tp1, c1) = collect(env,t)
(tpe1, (tp1, tpe2) :: c1)
def collectHelperAbs(env: Env, x: String, t: Term, tpe: Type): (Type, List[Constraint]) =
val (tp1, c1) = collect((x, TypeScheme(Nil,tpe)) :: env, t)
(FunType(tpe, tp1), c1)
def collectHelperIf(env: Env, t1: Term, t2: Term, t3: Term): (Type, List[Constraint]) =
val (tp1, c1) = collect(env, t1)
val (tp2, c2) = collect(env, t2)
val (tp3, c3) = collect(env, t3)
(tp2, (tp1, BoolType) :: (tp2, tp3) :: c1 ::: c2 ::: c3)
def collectHelperApp(env: Env, t1: Term, t2: Term, tpe: Type): (Type, List[Constraint]) =
val (tp1, c1) = collect(env, t1)
val (tp2, c2) = collect(env, t2)
(tpe, (tp1, FunType(tp2, tpe)) :: c1 ::: c2)
def collectHelperLet(env: Env, x: String, t1: Term, t2: Term): (Type, List[Constraint]) =
val (tp1, c1) = collect(env, t1)
val subst = unify(c1)
val tp1Unified = subst(tp1)
val fenv = env
.map((s,ts) => (s, ts.params, subst(ts.tp)))
.map((s,tsp,sub) => (s, TypeScheme(tsp.filter(_.in(sub)), sub)))
val ts = generalizeTV(tp1Unified, fenv)
val (tp2, c2) = collect((x, ts) :: fenv, t2)
(tp2, c1 ::: c2)
def collect(env: Env, t: Term): (Type, List[Constraint]) =
// println("Term: "+t)
// println("Env: "+env)
t match {
case True | False => (BoolType, Nil)
case Zero => (NatType, Nil)
case Pred(t1) => collectHelper(env, t1, NatType)
case Succ(t1) => collectHelper(env, t1, NatType)
case IsZero(t1) => collectHelper(env, t1, BoolType, NatType)
case If(t1, t2, t3) => collectHelperIf(env, t1, t2, t3)
case Var(x) if (env.exists(_._1 == x)) => ( env.find(_._1 == x).get._2.tp, Nil)
case Abs(x, tp, t1) => tp match {
case EmptyTypeTree() => collectHelperAbs(env, x, t1, freshTV())
case _ => collectHelperAbs(env, x, t1, tp.tpe)
}
case App(t1, t2) => collectHelperApp(env, t1, t2, freshTV())
case Let(x, tp, t1, t2) => tp match {
case EmptyTypeTree() => collectHelperLet(env, x, t1, t2)
case _ => collect(env, App(Abs(x, tp, t2), t1))
}
case _ => throw TypeError(f"Collect is stuck on term `$t` !")
}
def unify(c: List[Constraint]): Type => Type = {
val substitutions = unifyRec(c, Map())
// println("Subst: "+ substitutions)
return (x) => subst(x,substitutions)
}
def subst(tpe: Type, constraints: Map[Type, Type]): Type = tpe match {
case FunType(tpe1, tpe2) => FunType(subst(tpe1, constraints), subst(tpe2, constraints))
case a@TypeVar(_) if constraints.contains(a) => subst(constraints(a), constraints)
case others => others
}
def unifyRec(c: List[Constraint], substitutions: Map[Type, Type]): Map[Type, Type] = {
if (c.isEmpty)
return substitutions
// println("c: "+ c)
// println("Subst: "+ substitutions)
c.head match {
case (s, t) if s == t => unifyRec(c.tail, substitutions)
case (s@TypeVar(_), t) if !(s in t) => unifyRec(applyMapping(c.tail, s -> t), substitutions + (s -> t))
case (s, t@TypeVar(_)) if !(t in s) => unifyRec(applyMapping(c.tail, t -> s), substitutions + (t -> s))
case (FunType(s1, s2), FunType(t1, t2)) => unifyRec((s1, t1) :: (s2, t2) :: c.tail, substitutions)
case _ => throw new TypeError(f"Impossible to find a substitution that satisfies the constraint set `${c.head}`")
}
}
def applyMapping(constraints: List[Constraint], st: (TypeVar, Type)): List[Constraint] =
val (s, t) = st
def recMapping(tp: Type): Type = tp match {
case tp if tp == s => t
case FunType(a, b) => FunType(recMapping(a), recMapping(b))
case tp => tp
}
constraints.map {
case (`s`, `s`) => (t, t)
case (`s`, y) => (t, y)
case (x , `s`) => (x, t)
case (x, y) => (recMapping(x), recMapping(y))
}
}

View File

@@ -0,0 +1,25 @@
package fos
import Parser._
import scala.util.parsing.input._
object Launcher {
def main(args: Array[String]) = {
val stdin = new java.io.BufferedReader(new java.io.InputStreamReader(System.in))
val tokens = new lexical.Scanner(stdin.readLine())
phrase(term)(tokens) match {
case Success(term, _) =>
try {
val (tpe, c) = Infer.collect(Nil, term)
// println("TPE: "+tpe)
// println("C: "+c)
val sub = Infer.unify(c)
println("typed: " + sub(tpe))
} catch {
case tperror: Exception => println("type error: " + tperror.getMessage)
}
case e =>
println(e)
}
}
}

View File

@@ -0,0 +1,74 @@
package fos
import scala.util.parsing.combinator.syntactical.StandardTokenParsers
import scala.util.parsing.input._
object Parser extends StandardTokenParsers {
lexical.delimiters ++= List("(", ")", "\\", ".", ":", "=", "->", "{", "}", ",", "*", "+")
lexical.reserved ++= List("Bool", "Nat", "true", "false", "if", "then", "else", "succ",
"pred", "iszero", "let", "in")
/** <pre>
* Term ::= SimpleTerm { SimpleTerm }</pre>
*/
def term: Parser[Term] = positioned(
simpleTerm ~ rep(simpleTerm) ^^ { case t ~ ts => (t :: ts).reduceLeft[Term](App.apply) }
| failure("illegal start of term"))
/** <pre>
* SimpleTerm ::= "true"
* | "false"
* | number
* | "succ" Term
* | "pred" Term
* | "iszero" Term
* | "if" Term "then" Term "else" Term
* | ident
* | "\" ident [":" Type] "." Term
* | "(" Term ")"
* | "let" ident [":" Type] "=" Term "in" Term</pre>
*/
def simpleTerm: Parser[Term] = positioned(
"true" ^^^ True
| "false" ^^^ False
| numericLit ^^ { case chars => lit2Num(chars.toInt) }
| "succ" ~ term ^^ { case "succ" ~ t => Succ(t) }
| "pred" ~ term ^^ { case "pred" ~ t => Pred(t) }
| "iszero" ~ term ^^ { case "iszero" ~ t => IsZero(t) }
| "if" ~ term ~ "then" ~ term ~ "else" ~ term ^^ {
case "if" ~ t1 ~ "then" ~ t2 ~ "else" ~ t3 => If(t1, t2, t3)
}
| ident ^^ { case id => Var(id) }
| "\\" ~ ident ~ opt(":" ~ typ) ~ "." ~ term ^^ {
case "\\" ~ x ~ Some(":" ~ tp) ~ "." ~ t => Abs(x, tp, t)
case "\\" ~ x ~ None ~ "." ~ t => Abs(x, EmptyTypeTree(), t)
}
| "(" ~> term <~ ")" ^^ { case t => t }
| "let" ~ ident ~ opt(":" ~ typ) ~ "=" ~ term ~ "in" ~ term ^^ {
case "let" ~ x ~ Some(":" ~ tp) ~ "=" ~ t1 ~ "in" ~ t2 => Let(x, tp, t1, t2)
case "let" ~ x ~ None ~ "=" ~ t1 ~ "in" ~ t2 => Let(x, EmptyTypeTree(), t1, t2)
}
| failure("illegal start of simple term"))
/** <pre>
* Type ::= SimpleType { "->" Type }</pre>
*/
def typ: Parser[TypeTree] = positioned(
baseType ~ opt("->" ~ typ) ^^ {
case t1 ~ Some("->" ~ t2) => FunTypeTree(t1, t2)
case t1 ~ None => t1
}
| failure("illegal start of type"))
/** <pre>
* BaseType ::= "Bool" | "Nat" | "(" Type ")"</pre>
*/
def baseType: Parser[TypeTree] = positioned(
"Bool" ^^^ BoolTypeTree()
| "Nat" ^^^ NatTypeTree()
| "(" ~> typ <~ ")" ^^ { case t => t }
)
private def lit2Num(n: Int): Term =
if (n == 0) Zero else Succ(lit2Num(n - 1))
}

View File

@@ -0,0 +1,83 @@
package fos
import scala.util.parsing.input.Positional
sealed abstract class Term extends Positional
case object True extends Term {
override def toString() = "true"
}
case object False extends Term {
override def toString() = "false"
}
case object Zero extends Term {
override def toString() = "0"
}
case class Succ(t: Term) extends Term {
override def toString() = "succ " + t
}
case class Pred(t: Term) extends Term {
override def toString() = "pred " + t
}
case class IsZero(t: Term) extends Term {
override def toString() = "iszero " + t
}
case class If(cond: Term, t1: Term, t2: Term) extends Term {
override def toString() = "if " + cond + " then " + t1 + " else " + t2
}
case class Var(name: String) extends Term {
override def toString() = name
}
case class Abs(v: String, tp: TypeTree, t: Term) extends Term {
override def toString() = "(\\" + v + ":" + tp + "." + t + ")"
}
case class App(t1: Term, t2: Term) extends Term {
override def toString() = t1.toString + (t2 match {
case App(_, _) => " (" + t2.toString + ")" // left-associative
case _ => " " + t2.toString
})
}
case class Let(x: String, tp: TypeTree, v: Term, t: Term) extends Term {
override def toString() = "let " + x + ":" + tp + " = " + v + " in " + t
}
// Note that TypeTree is distinct from Type.
// The former is how types are parsed, the latter is how types are represented.
// We need this distinction because:
// 1) There are type vars, which can't be written by our users, but are needed by the inferencer.
// 2) There are empty types, which can be written, but aren't directly supported by the inferencer.
abstract class TypeTree extends Positional {
def tpe: Type
}
case class BoolTypeTree() extends TypeTree {
override def tpe = BoolType
override def toString() = "Bool"
}
case class NatTypeTree() extends TypeTree {
override def tpe = NatType
override def toString() = "Nat"
}
case class FunTypeTree(t1: TypeTree, t2: TypeTree) extends TypeTree {
override def tpe = FunType(t1.tpe, t2.tpe)
override def toString() = (t1 match {
case FunTypeTree(_, _) => "(" + t1 + ")" // right-associative
case _ => t1.toString
}) + "->" + t2
}
case class EmptyTypeTree() extends TypeTree {
override def tpe = throw new UnsupportedOperationException
override def toString() = "_"
}

View File

@@ -0,0 +1,22 @@
package fos
// Note that TypeTree is distinct from Type.
// See a comment on TypeTree to learn more.
abstract class Type
case class TypeVar(name: String) extends Type {
override def toString() = name
}
case class FunType(t1: Type, t2: Type) extends Type {
override def toString() = "(" + t1 + " -> " + t2 + ")"
}
case object NatType extends Type {
override def toString() = "Nat"
}
case object BoolType extends Type {
override def toString() = "Bool"
}

View File

@@ -0,0 +1,280 @@
#!/usr/bin/env python3
# This script was contributed by Timothée Loyck Andres.
import argparse
import json
import os.path
import pathlib
import shutil
import ssl
import tempfile
from email import encoders
from email.mime.base import MIMEBase
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from getpass import getpass
from smtplib import SMTP_SSL, SMTPAuthenticationError, SMTPDataError
from typing import List, Optional, Any, Union
# ========================= CONFIG VARIABLES =========================
SMTP_SERVER = 'mail.epfl.ch'
BOT_EMAIL_ADDRESS = 'lamp.fos.bot@gmail.com'
# The minimum number of people in a group
MIN_SCIPERS = 1
# The maximum number of people in a group
MAX_SCIPERS = 3
# The number of the project (set to None to prompt user)
PROJECT_NUMBER = None
# The name of the file in which the submission configuration is stored
CONFIG_FILE_NAME = '.submission_info.json'
# The name of the folder to be zipped
SRC_FOLDER = 'src'
# ====================================================================
project_path = pathlib.Path(__file__).parent.resolve()
config_path = f'{project_path}/{CONFIG_FILE_NAME}'
src_path = f'{project_path}/{SRC_FOLDER}'
tmp_folder = tempfile.gettempdir()
class ConfigData:
def __init__(self, data_values: Optional[dict] = None, **kwargs):
"""
Creates a new data object with the given values. A dictionary may be passed, or keyword arguments for each data
piece.
Contains: email address, username, SCIPER numbers of the group's members, project number
:argument data_values: an optional dictionary containing the required data
:keyword email: the email of the user
:keyword username: the GASPAR id of the user
:keyword scipers: the list of SCIPER numbers of the group's members
:keyword project_num: the project's number
"""
if data_values is not None:
data = data_values
elif len(kwargs) > 0:
data = kwargs
else:
data = dict()
self.email: str = data.get('email')
self.username: str = data.get('username')
self.scipers: List[str] = data.get('scipers')
self.project_num: int = data.get('project_num')
def get_config_data(self) -> dict:
return {
'email': self.email,
'username': self.username,
'scipers': self.scipers,
'project_num': self.project_num
}
def get_scipers() -> List:
"""
Retrieves the group's SCIPER numbers.
:return: a list containing the SCIPER numbers of all the members of the FoS group
"""
def is_sciper(string: str) -> bool:
try:
return len(string) == 6 and int(string) > 0
except TypeError:
return False
num_scipers = None
scipers: List[str] = []
while num_scipers is None:
num_scipers = get_sanitized_input(
"Number of people in the group: ",
int,
predicate=lambda n: MIN_SCIPERS <= n <= MAX_SCIPERS,
predicate_error_msg=f"The number of people must be between {MIN_SCIPERS} and {MAX_SCIPERS} included."
)
for i in range(num_scipers):
sciper = None
while sciper is None:
sciper = get_sanitized_input(
f"SCIPER {i + 1}: ",
predicate=is_sciper,
predicate_error_msg="Invalid SCIPER number. Please try again."
)
scipers.append(sciper)
return scipers
def get_sanitized_input(prompt: str, value_type: type = str, **kwargs) -> Optional[Any]:
"""
Sanitizes the user's input.
:param prompt: the message to be displayed for the user
:param value_type: the type of value that we expect, for example str or int
:keyword allow_empty: allow the input to be empty. The returned string may be the empty string
:keyword predicate: a function that, when applied to the sanitized input, checks if it is valid
:keyword predicate_error_msg: a message to be displayed if the predicate returns false on the input
:return: the input as the passed type, or None if the input contained only whitespaces or if the type cast failed
"""
str_value = input(prompt).strip()
if len(str_value) > 0 or kwargs.get('allow_empty'):
try:
value = value_type(str_value)
p = kwargs.get('predicate')
if p is not None and not p(value):
if kwargs.get('predicate_error_msg') is None:
print("Invalid value. Please try again.")
elif len(kwargs.get('predicate_error_msg')) > 0:
print(kwargs.get('predicate_error_msg'))
return None
return value
except TypeError:
raise TypeError(f"Incorrect value type: {value_type}")
except ValueError:
print(f"The value could not be interpreted as type {value_type.__name__}. Please try again.")
return None
def get_config(from_file: bool = True) -> ConfigData:
"""
Retrieves the configuration for sending the email. It may be fetched from a configuration file, or if it does not
exist or is incomplete, it will ask the user for the data, then write it to the config file.
:param from_file: whether to retrieve the configuration from the config file if it exists. Default is True
:return: the configuration to use for the email
"""
data = ConfigData()
# Set project number if it is already specified
data.project_num = PROJECT_NUMBER
if from_file and not os.path.isfile(config_path):
print('Please provide data that will be used to submit your project.')
print(f'This information (sans the password) will be saved in: ./{CONFIG_FILE_NAME}')
if from_file and os.path.isfile(config_path):
with open(config_path, 'r') as config_file:
config = json.load(config_file)
if type(config) is dict:
data = ConfigData(config)
if data.scipers is None:
data.scipers = get_scipers()
while data.email is None:
data.email = get_sanitized_input("Email address: ", predicate=lambda address: '@' in address)
while data.username is None:
data.username = get_sanitized_input("Gaspar ID: ")
while data.project_num is None:
data.project_num = get_sanitized_input("Project number: ", int, predicate=lambda n: n > 0)
set_config(data)
return data
def set_config(data: ConfigData) -> None:
"""
Saves the configuration in the config file.
:param data: the data to be saved
"""
with open(config_path, 'w') as config_file:
json.dump(data.get_config_data(), config_file)
def create_email(frm: str, to: str, subject: str, content: Optional[str] = None,
attachments: Optional[Union[str, List[str]]] = None) -> MIMEMultipart:
"""
Creates an email.
:param frm: the address from which the email is sent
:param to: the address to which send the email
:param subject: the subject of the email
:param content: the content of the email. Can be empty
:param attachments: the attachments of the email. Can be a path or a list of paths
"""
message = MIMEMultipart()
message['From'] = frm
message['To'] = to
message['Subject'] = subject
if content is not None:
# Add content into body of message
message.attach(MIMEText(content, 'plain'))
if attachments is not None:
if type(attachments) is str:
attachments = [attachments]
for attachment_path in attachments:
part = MIMEBase("application", "octet-stream")
with open(attachment_path, 'rb') as attachment:
part.set_payload(attachment.read())
encoders.encode_base64(part)
part.add_header("Content-Disposition", f"attachment; filename={os.path.basename(attachment_path)}")
message.attach(part)
return message
if __name__ == '__main__':
arg_parser = argparse.ArgumentParser(description="Submits the project to the bot for grading.", allow_abbrev=False)
arg_parser.add_argument('-r', '--reset', action='store_true', help="ask for the submission data even if previously "
"specified")
arg_parser.add_argument('-s', '--self', action='store_true', help="send the mail to yourself instead of the bot")
if not os.path.isdir(src_path):
arg_parser.exit(1, f"No {SRC_FOLDER} folder found. Aborting.\n")
args = arg_parser.parse_args()
config: ConfigData = get_config(from_file=not args.reset)
password: str = getpass("Gaspar password: ")
recipient = config.email if args.self else BOT_EMAIL_ADDRESS
mail = create_email(
config.email,
recipient,
f"Project {config.project_num} ({', '.join(config.scipers)})",
attachments=shutil.make_archive(f'{tmp_folder}/{SRC_FOLDER}', 'zip', root_dir=project_path,
base_dir=f'{SRC_FOLDER}')
)
with SMTP_SSL(SMTP_SERVER, context=ssl.create_default_context()) as server:
try:
server.login(config.username, password)
server.sendmail(config.email, recipient, mail.as_string())
print(f"Submission sent to {recipient}.")
except SMTPAuthenticationError as e:
if e.smtp_code == 535:
print(f"Wrong GASPAR ID ({config.username}) or password. Your ID will be asked for again on the next"
" run.")
# Remove (potentially) incorrect ID from config
config.username = None
set_config(config)
exit(2)
else:
raise
except SMTPDataError as e:
if e.smtp_code == 550:
print("You email address seems to be incorrect. It will be asked for again on the next run.")
# Remove incorrect address from config
config.email = None
set_config(config)
exit(2)
else:
raise