220 lines
5.7 KiB
Python
220 lines
5.7 KiB
Python
from lark import Lark, Transformer, v_args
|
|
import json
|
|
|
|
|
|
|
|
"""describe college courses, their number of units, any other courses that are prerequisites, as well as various degrees which
|
|
consist of groups of courses that must be taken. The groups have rules associated with them, such as:
|
|
- take all (the following courses)
|
|
- take n (where n=3 or 5 or something)
|
|
- take at least n units from the following list (so the taken course's unit value must add up to n or greater)
|
|
- and so on."""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Course:
|
|
def __init__(self, name, units, prerequisites=[]):
|
|
self.name = name
|
|
self.units = units
|
|
self.prerequisites = prerequisites
|
|
def __repr__(self):
|
|
return f"{self.name} ({self.units} units)"
|
|
|
|
class DegreeRule:
|
|
def __init__(self, rule_type, course_list):
|
|
self.rule_type = rule_type
|
|
if 'n' in rule_type.__dict__: self.n = rule_type.n
|
|
self.course_list = course_list
|
|
def __repr__(self):
|
|
return f"DegreeRule({self.rule_type}, {self.course_list})"
|
|
|
|
class Degree:
|
|
def __init__(self, name, degree_rules):
|
|
self.name = name
|
|
self.degree_rules = degree_rules
|
|
def __repr__(self):
|
|
return f"Degree({self.name}, {self.degree_rules})"
|
|
|
|
class RuleType():
|
|
def __init__(self,what,n=0):
|
|
self.what = what
|
|
self.n = n
|
|
def __repr__(self):
|
|
return "" #f"RuleType({self.what}, {self.n})"
|
|
|
|
class Rule:
|
|
pass
|
|
|
|
class TakeAll(Rule):
|
|
def __repr__(self):
|
|
return f"Take all:"
|
|
|
|
class TakeN(Rule):
|
|
def __init__(self, n):
|
|
self.n = n
|
|
def __repr__(self):
|
|
return f"Take {self.n} courses:"
|
|
|
|
class TakeNUnits(Rule):
|
|
def __init__(self, n):
|
|
self.n = n
|
|
def __repr__(self):
|
|
return f"Take {self.n} units:"
|
|
|
|
|
|
@v_args(inline=True)
|
|
class DSLTransformer(Transformer):
|
|
def __init__(self):
|
|
self.courses = {}
|
|
self.degrees = {}
|
|
|
|
def course_declaration(self, name, units):
|
|
#return Course(name, int(units), prerequisites.children)
|
|
self.courses[name.value] = Course(name.value, int(units))
|
|
|
|
'''def program(self, name, rules):
|
|
self.degrees[name.value] = Degree(name.value, rules)
|
|
return self.degrees[name.value]'''
|
|
|
|
def rule_type(self, items):
|
|
what = items[0]
|
|
n = None
|
|
if len(items) == 2:
|
|
n = int(items[1])
|
|
return RuleType(what, n)
|
|
|
|
def take_all(self):
|
|
return TakeAll()
|
|
|
|
def take_n_courses(self, n):
|
|
return TakeN(n)
|
|
|
|
def take_n_units(self, n):
|
|
return TakeNUnits(n)
|
|
|
|
def degree_rule(self, rule_type, course_list):
|
|
return DegreeRule(rule_type, course_list)
|
|
|
|
def course_list(self, *courses):
|
|
return [c.value for c in courses]
|
|
|
|
def program(self, *items):
|
|
#print(items)
|
|
name = items[0].value
|
|
rules = items[1:]
|
|
self.degrees[name] = Degree(name, rules)
|
|
return self.degrees[name]
|
|
|
|
"""
|
|
dsl = DSLTransformer()
|
|
dsl.course('CS101', 3)
|
|
dsl.course('CS102', 3, prerequisites=['CS101'])
|
|
dsl.course('MATH101', 3)
|
|
dsl.degree('CS', [
|
|
dsl.take_all(['CS101', 'CS102']),
|
|
dsl.take_n_units(['CS101', 'CS102', 'MATH101'], 9),
|
|
dsl.take_n(['CS101', 'CS102', 'MATH101'], 2),
|
|
])
|
|
|
|
print(dsl.courses)
|
|
print(dsl.degrees)
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
grammar = """
|
|
start: _spec+
|
|
|
|
_spec: program | course_declaration
|
|
|
|
program: "program" PROGRAMNAME degree_rule*
|
|
|
|
degree_rule: "take" rule_type course_list
|
|
|
|
rule_type: "all from" -> take_all
|
|
| "at least" INT "units from" -> take_n_units
|
|
| "at least" INT "courses from" -> take_n_courses
|
|
|
|
course_declaration: "course" COURSECODE INT "units" ["prerequisites" (COURSECODE ","?)*]
|
|
|
|
course_list: (COURSECODE ","?)*
|
|
|
|
COURSECODE: ("A".."Z")+ INT+ ["A".."Z"]*
|
|
PROGRAMNAME: ("A".."Z" | "a".."z")+
|
|
%import common.INT
|
|
%import common.WS
|
|
%ignore WS
|
|
"""
|
|
|
|
|
|
|
|
dsl = """
|
|
course CSIS1 2 units
|
|
course CSIS2 4 units
|
|
course ACCT120 3 units
|
|
course ACCT20 4 units
|
|
course BUS1 3 units
|
|
course ENGL250 3 units
|
|
course ACCT105 3 units
|
|
course ACCT121 3 units
|
|
course BOT100 3 units
|
|
course BOT105B 3 units
|
|
|
|
program BusinessAccountingOption
|
|
take all from CSIS1, CSIS2
|
|
take at least 6 units from ACCT120, ACCT20
|
|
take at least 3 courses from BUS1, ENGL250, ACCT120, BOT100, ACCT121, BOT105B
|
|
"""
|
|
|
|
parser = Lark(grammar)
|
|
print(parser.parse(dsl).pretty())
|
|
|
|
print("\n\n\n")
|
|
|
|
|
|
parser = Lark(grammar)
|
|
transformer = DSLTransformer()
|
|
|
|
def parse_dsl(dsl):
|
|
tree = parser.parse(dsl)
|
|
return transformer.transform(tree)
|
|
|
|
|
|
result = parse_dsl(dsl)
|
|
|
|
print(transformer.courses)
|
|
print()
|
|
print(transformer.degrees)
|
|
print()
|
|
[print(c) for c in transformer.courses]
|
|
print()
|
|
[print(d) for d in transformer.degrees]
|
|
|
|
"""
|
|
|
|
|
|
def compute_degree_progress(student_courses, degree):
|
|
total_units_completed = 0
|
|
total_required_units = 0
|
|
for course in degree['required_courses']:
|
|
if course['id'] in student_courses:
|
|
total_units_completed += course['units']
|
|
if 'additional_rules' in course:
|
|
for rule in course['additional_rules']:
|
|
if rule['type'] == 'at_least_n_units_from_list':
|
|
units_from_list = sum([c['units'] for c in degree['courses'] if c['id'] in student_courses and c['id'] in rule['course_list']])
|
|
if units_from_list < rule['n']:
|
|
break
|
|
total_required_units += course['units']
|
|
progress_percent = total_units_completed / total_required_units * 100 if total_required_units > 0 else 0
|
|
return progress_percent
|
|
|
|
""" |