from lark import Lark, Transformer, v_args """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.""" grammar = """ start: program program: course_declaration* course_declaration: "course" CNAME INT "prerequisites" (CNAME ","?)* "degree" CNAME "{" degree_rule* "}" degree_rule: "take" ("all" | "at" INT "from") ("the" "following") "courses" course_list course_list: (CNAME ","?)* %import common.CNAME %import common.INT %import common.WS %ignore WS """ class Course: def __init__(self, name, units, prerequisites, degree): self.name = name self.units = units self.prerequisites = prerequisites self.degree = degree class DegreeRule: def __init__(self, verb, n, course_list): self.verb = verb self.n = n self.course_list = course_list class Degree: def __init__(self, name, degree_rules): self.name = name self.degree_rules = degree_rules class Rule: pass class TakeAll(Rule): def __init__(self, courses): self.courses = courses class TakeN(Rule): def __init__(self, courses, n): self.courses = courses self.n = n class TakeNUnits(Rule): def __init__(self, courses, n): self.courses = courses self.n = n @v_args(inline=True) class DSLTransformer(Transformer): def __init__(self): self.courses = {} self.degrees = {} def course_declaration(self, name, units, prerequisites, degree): return Course(name, int(units), prerequisites.children, degree) def degree(self, name, required_courses): self.degrees[name] = Degree(name, required_courses) def take_all(self, courses): return TakeAll(courses) def take_n(self, courses, n): return TakeN(courses, n) def take_n_units(self, courses, n): return TakeNUnits(courses, n) def degree_rule(self, items): verb = items[0] n = None course_list = items[-1] if len(items) == 4: n = int(items[2]) return DegreeRule(verb, n, course_list.children) def course_list(self, items): return items[:-1] def program(self, items): courses = [item for item in items if isinstance(item, Course)] degrees = [item for item in items if isinstance(item, Degree)] return courses, degrees 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) parser = Lark(grammar) transformer = DSLTransformer() def parse_dsl(dsl): tree = parser.parse(dsl) return transformer.transform(tree) dsl = """ course CS101 3 prerequisites none degree CS { take all the following courses { CS102, CS103 } take at least 6 from { MATH101, MATH102, MATH103 } } """ courses, degrees = parse_dsl(dsl) print(courses) print(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