Skip to content

Instantly share code, notes, and snippets.

@hawkrives
Last active September 25, 2024 18:28
Hanson to JS to Boolean

This current version is very much broken up between several parts.

Eventually, the Hanson file will go in, and a JSON file will come out; then evaluate will take the JSON, a list of courses, and a map of overrides, and return a mutated ("enhanced") object with enough info (hopefully) to render the sidebar info pane.

Currently, though, the glue between the PEGjs parser and the file reader isn't written, so it has to be done manually.

Each "result" string must be fed into the peg, then copied back into the Hanson file. The Hanson file also needs to have types assigned to the requirements themselves, and enough braces put in that it becomes valid JSON.

Might be easiest to use the online JS-yaml tool at http://nodeca.github.io/js-yaml, then copy each result string into the online PEGjs parser at http://pegjs.org/online, then copy it back to replace the result string with the result object.

Lots o' work, right now. Yeppers.

# data.py
# coding: utf-8
data = {
"Studio Art": {
"$type": "requirement",
"Studio Art Courses": {
"$type": "requirement",
"result": {
"$type": "of",
"$count": 8,
"$of": [
{"$type": "course", "department": ["ART"], "number": 102},
{"$type": "course", "department": ["ART"], "number": 103},
{"$type": "course", "department": ["ART"], "number": 104},
{"$type": "course", "department": ["ART"], "number": 106},
{"$type": "course", "department": ["ART"], "number": 205},
{"$type": "course", "department": ["ART"], "number": 207},
{"$type": "course", "department": ["ART"], "number": 221},
{"$type": "course", "department": ["ART"], "number": 222},
{"$type": "course", "department": ["ART"], "number": 223},
{"$type": "course", "department": ["ART"], "number": 224},
{"$type": "course", "department": ["ART"], "number": 225},
{"$type": "course", "department": ["ART"], "number": 226},
{"$type": "course", "department": ["ART"], "number": 227},
{"$type": "course", "department": ["ART"], "number": 228},
{"$type": "course", "department": ["ART"], "number": 229},
{"$type": "course", "department": ["ART"], "number": 232},
{"$type": "course", "department": ["ART"], "number": 233},
{"$type": "course", "department": ["ART"], "number": 234},
{"$type": "course", "department": ["ART"], "number": 236},
{"$type": "course", "department": ["ART"], "number": 238},
{"$type": "course", "department": ["ART"], "number": 240},
{"$type": "course", "department": ["ART"], "number": 246},
{"$type": "course", "department": ["ART"], "number": 294},
{"$type": "course", "department": ["ART"], "number": 298},
{"$type": "course", "department": ["ART"], "number": 340},
{"$type": "course", "department": ["ART"], "number": 343},
{"$type": "course", "department": ["ART"], "number": 394},
{"$type": "course", "department": ["ART"], "number": 398}
]
}
},
"Foundations": {
"$type": "requirement",
"result": {
"$type": "of",
"$count": 3,
"$of": [
{"$type": "course", "department": ["ART"], "number": 102},
{"$type": "course", "department": ["ART"], "number": 103},
{"$type": "course", "department": ["ART"], "number": 104}
]
}
},
"2D Media": {
"$type": "requirement",
"Drawing": {
"$type": "requirement",
"result": {
"$type": "boolean",
"$or": [
{"$type": "course", "department": ["ART"], "number": 225},
{"$type": "course", "department": ["ART"], "number": 232},
{"$type": "course", "department": ["ART"], "number": 233}
]
}
},
"Painting": {
"$type": "requirement",
"result": {
"$type": "boolean",
"$or": [
{"$type": "course", "department": ["ART"], "number": 232},
{"$type": "course", "department": ["ART"], "number": 233}
]
}
},
"Printmaking": {
"$type": "requirement",
"result": {
"$type": "boolean",
"$or": [
{"$type": "course", "department": ["ART"], "number": 226},
{"$type": "course", "department": ["ART"], "number": 227}
]
}
},
"result": {
"$type": "of",
"$count": 1,
"$of": [
{"$type": "reference", "$requirement": "Drawing"},
{"$type": "reference", "$requirement": "Painting"},
{"$type": "reference", "$requirement": "Printmaking"}
]
}
},
"3D Media": {
"$type": "requirement",
"Ceramics": {
"$type": "requirement",
"result": {
"$type": "boolean",
"$or": [
{"$type": "course", "department": ["ART"], "number": 207},
{"$type": "course", "department": ["ART"], "number": 234}
]
}
},
"Sculpture": {
"$type": "requirement",
"result": {
"$type": "boolean",
"$or": [
{"$type": "course", "department": ["ART"], "number": 223},
{"$type": "course", "department": ["ART"], "number": 224}
]
}
},
"result": {
"$count": 1,
"$type": "of",
"$of": [
{"$type": "reference", "$requirement": "Ceramics"},
{"$type": "reference", "$requirement": "Scuplture"}
]
}
},
"New Media": {
"$type": "requirement",
"Photography": {
"$type": "requirement",
"result": {
"$type": "boolean",
"$or": [
{"$type": "course", "department": ["ART"], "number": 205},
{"$type": "course", "department": ["ART"], "number": 238}
]} },
"Interactive Image": {"$type": "requirement", "result": {"$type": "course", "department": ["ART"], "number": 228} },
"Digital Video": {"$type": "requirement", "result": {"$type": "course", "department": ["ART"], "number": 229} },
"Performance": {"$type": "requirement", "result": {"$type": "course", "department": ["ART"], "number": 240} },
"Graphic Design": {"$type": "requirement", "result": {"$type": "course", "department": ["ART"], "number": 236} },
"result": {
"$count": 1,
"$type": "of",
"$of": [
{"$type": "reference", "$requirement": "Photography"},
{"$type": "reference", "$requirement": "Interactive Image"},
{"$type": "reference", "$requirement": "Digital Video"},
{"$type": "reference", "$requirement": "Performance"},
{"$type": "reference", "$requirement": "Graphic Design"},
]
}
},
"Juried Show": {"$type": "requirement", "message": "To fulfill the requirements of the Studio Art major, you must enter at least two juried art exhibitions on or off campus by the beginning of your senior year."},
"Senior Thing": {"$type": "requirement", "result": {"$type": "course", "department": ["ART"], "number": 343}},
"result": {
"$count": 7,
"$type": "of",
"$of": [
{"$type": "reference", "$requirement": "Foundations"},
{"$type": "reference", "$requirement": "2D Media"},
{"$type": "reference", "$requirement": "3D Media"},
{"$type": "reference", "$requirement": "New Media"},
{"$type": "reference", "$requirement": "Juried Show"},
{"$type": "reference", "$requirement": "Senior Thing"},
{"$type": "reference", "$requirement": "Studio Art Courses"}
]
}
},
"Art History": {
"$type": "requirement",
"message": "The department strongly recommends that you take ART 252 or 253 as one of your art history courses.",
"result": {
"$count": 2,
"$type": "of",
"$of": [
{"$type": "course", "department": ["ART"], "number": 153},
{"$type": "course", "department": ["ART"], "number": 161},
{"$type": "course", "department": ["ART"], "number": 251},
{"$type": "course", "department": ["ART"], "number": 252},
{"$type": "course", "department": ["ART"], "number": 253},
{"$type": "course", "department": ["ART"], "number": 254},
{"$type": "course", "department": ["ART"], "number": 255},
{"$type": "course", "department": ["ART"], "number": 256},
{"$type": "course", "department": ["ART"], "number": 263},
{"$type": "course", "department": ["ART"], "number": 269},
{"$type": "course", "department": ["ART"], "number": 271},
{"$type": "course", "department": ["ART"], "number": 275},
{"$type": "course", "department": ["ART"], "number": 277},
{"$type": "course", "department": ["ART"], "number": 280},
{"$type": "course", "department": ["ART"], "number": 294},
{"$type": "course", "department": ["ART"], "number": 298},
{"$type": "course", "department": ["ART"], "number": 350},
{"$type": "course", "department": ["ART"], "number": 379},
{"$type": "course", "department": ["ART"], "number": 394},
{"$type": "course", "department": ["ART"], "number": 396},
{"$type": "course", "department": ["ART"], "number": 398},
{"$type": "course", "department": ["ART", "ASIAN"], "number": 259},
{"$type": "course", "department": ["ART", "ASIAN"], "number": 260},
{"$type": "course", "department": ["ART", "ASIAN"], "number": 262},
{"$type": "course", "department": ["ART", "ASIAN"], "number": 270},
{"$type": "course", "department": ["ART", "ASIAN"], "number": 310},
{"$type": "course", "department": ["ENVST"], "number": 270},
{"$type": "course", "department": ["PHIL"], "number": 243},
{"$type": "course", "department": ["RACE"], "number": 253}
]
}
},
"Art Credits": {
"$type": "requirement",
"result": {
"$type": "where",
"$count": 10,
"$what": "credit",
"$from": "where",
"$where": {
"$type": "qualifier",
"$key": "department",
"$value": {
"$type": "operator",
"$eq": "ART"
}
}
}
},
"name": "Studio Art",
"type": "Major",
"revision": "2014-15",
"result": {
"$type": "boolean",
"$and": [
{ "$type": "reference", "$requirement": "Studio Art" },
{ "$type": "reference", "$requirement": "Art History" },
{ "$type": "reference", "$requirement": "Art Credits" }
]
}
}
overrides = {
"major.studio art.studio art.juried show": True
}
# compute.py
# coding: utf-8
import re
import pprint
from functools import reduce
from data import data, overrides
# - support where queries
# - [ ] basic queries
# - [ ] boolean sets of queries
# - [ ] nested queries
# - write tests
class RequiredKeyException(Exception):
def __init__(self, keys=[], msg='', data={}):
self.value = keys or msg
self.data = data
def __str__(self):
return 'missing keys: ' + repr(self.value) + str(self.data)
class UnknownPropertyException(Exception):
def __init__(self, value):
self.value = value
def __str__(self):
return 'unknown value: ' + repr(self.value)
class BadTypeException(Exception):
def __init__(self, msg, data):
self.value = msg
self.data = data
def __str__(self):
return self.value + ' ' + repr(self.data)
def pluck(list, prop):
return [i[prop]
for i in list
if prop in i]
def add(a, b):
return a + b
def sum(iter):
return reduce(add, iter, 0)
# Helper Functions
def isRequirement(name):
return bool(re.match(r'[A-Z]|[0-9][A-Z\- ]', name))
def compare_course(course, to):
# course might have more keys than the dict we're comparing it to
# 'to' will have some combination of 'year', 'semester', 'department', 'number', and 'section'
for key in ['year', 'semester', 'department', 'number', 'section']:
if key in to and course[key] != to[key]:
return False
return True
#compared = {key: val
# for key, val in course.items()
# if to.get(key, False)}
#return bool(compared)
def checkForCourse(filter, courses):
return any([c for c in courses if compare_course(course=c, to=filter)])
def get_occurrences(course, courses):
return [c for c in courses if compare_course(course=c, to=filter)]
def assertKeys(dict, *keys):
missingKeys = [key
for key in keys
if key not in dict]
if missingKeys:
raise RequiredKeyException(keys=missingKeys, data=dict)
def countCourses(courses):
#courses::pluck('crsid')::uniq()::len()
return len(set(pluck(courses, 'crsid')))
def countDepartments(courses):
# courses::pluck('departments')::sum()
return sum(pluck(courses, 'departments'))
def countCredits(courses):
return sum(pluck(courses, 'credits'))
def pathToOverride(path):
return '.'.join(path).lower()
def hasOverride(path, overrides):
#print pathToOverride(path), overrides, pathToOverride(path) in overrides
return pathToOverride(path) in overrides
def getOverride(path, overrides):
return overrides[pathToOverride(path)]
def findOperatorType(operator):
if '$eq' in operator:
return '$eq'
elif '$ne' in operator:
return '$ne'
elif '$lt' in operator:
return '$lt'
elif '$lte' in operator:
return '$lte'
elif '$gt' in operator:
return '$gt'
elif '$gte' in operator:
return '$gte'
else:
raise RequiredKeyException(msg='no valid operators ($eq, $ne, $lt, $lte, $gt, $gte) could be found', data=operator)
def compareCourseAgainstOperator(course, key, operator):
# key: gereqs, operator: {$eq: "EIN"}
# key: year, operator: {
# "$type": "operator",
# "$lte": {
# "$name": "max",
# "$prop": "year",
# "$type": "function",
# "$where": [{
# "$type": "qualification", "gereqs": {
# "$type": "operator", "$eq": "BTS-T"
# }
# }]
# }
# } }
kind = findOperatorType(operator)
if type(operator[kind]) is dict:
# we compute the value of the function-over-where-query style operators
# earlier, in the filterByQualification function.
assertKeys(operator[kind], '$computed-value')
simplifiedOperator = {kind: operator[kind]['$computed-value']}
return compareCourseAgainstOperator(course, key, simplifiedOperator)
elif type(operator[kind]) is list:
raise BadTypeException(msg='what would a comparison to a list even do? oh, wait; i suppose it could compare against one of several values... well, im not doing that right now. if you want it, edit the PEG and stick appropriate stuff in here (probably simplest to just call this function again with each possible value and return true if any are true.)')
else:
# it's a static value; a number or string
if kind == '$eq':
return (course[key] == operator[kind]
if type(course[key]) is not list
else operator[kind] in course[key])
elif kind == '$ne':
return (course[key] != operator[kind]
if type(course[key]) is not list
else operator[kind] not in course[key])
elif kind == '$lt':
return course[key] < operator[kind]
elif kind == '$lte':
return course[key] <= operator[kind]
elif kind == '$gt':
return course[key] > operator[kind]
elif kind == '$gte':
return course[key] >= operator[kind]
def filterByQualification(list, qualification):
# { "$type":"qualification", $key: "gereqs", $value: {"$type": "operator", "$eq": "EIN"} }
# { "$type":"qualification", $key: "year", value: {
# "$type": "operator",
# "$lte": {
# "$name": "max",
# "$prop": "year",
# "$type": "function",
# "$where": {
# "$type": "qualification", $key: "gereqs", $value: {
# "$type": "operator", "$eq": "BTS-T"
# }
# }
# }
# } }
operator = qualification['$value']
kind = findOperatorType(operator)
if type(operator[kind]) is dict:
value = operator[kind]
if value['$type'] == 'function':
func = None
if value['$name'] == 'max':
func = max
elif value['$name'] == 'min':
func = min
filtered = filterfilterByWhereClause(value['$where'])
items = pluck(value['$prop'], filtered)
computed = func(items)
value['$computed-value'] = computed
print qualification
key = qualification['$key']
operator = qualification['$value']
filtered = [course
for course in list
if compareCourseAgainstOperator(course, key, operator)]
#if compareQualificationToCourse(qualification, course)]
print len(list), len(filtered)
return filtered
def filterByWhereClause(list, clause):
# {gereqs = EIN & year <= max(year) from {gereqs = BTS-T}}
# {
# "$type": "boolean",
# "$and": [
# { "$type":"qualification", $key: "gereqs", $value: {"$type": "operator", "$eq": "EIN"} },
# { "$type":"qualification", $key: "year", $value: {
# "$type": "operator",
# "$lte": {
# "$name": "max",
# "$prop": "year",
# "$type": "function",
# "$where": {
# "$type": "qualification", $key: "gereqs", $value: {
# "$type": "operator", "$eq": "BTS-T"
# }
# }
# }
# } }
# ]
# }
if clause['$type'] == 'qualifier':
#print 'qualification'
return filterByQualification(list, clause)
elif clause['$type'] == 'boolean':
#print 'boolean'
if '$and' in clause:
filtered = list
for q in clause['$and']:
filtered = filterByWhereClause(filtered, q)
return filtered
elif '$or' in clause:
filtrations = set()
for q in clause['$or']:
filtrations = filtrations.union(filterByWhereClause(list, q))
return filtrations
else:
raise RequiredKeyException(msg='neither $or nor $and could be found', data=clause)
else:
raise BadTypeException(msg='wth kind of type is this clause?', data=clause)
# Compute Functions:
# There are two types of compute functions: those that need the surrounding context, and those that don't.
# Contained Computes:
# course, occurrence, where
def compute_course(expr, courses):
query = {key: value
for key, value in expr.items()
if key not in ['$type']}
return checkForCourse(filter=query, courses=courses)
def compute_occurrence(expr, courses):
assertKeys(expr, '$course', '$count')
clause = {key: val
for key, val in expr['$course']
if key in ['department', 'number', 'section']}
filtered = get_occurrences(clause, courses)
return len(filtered) >= expr['$count']
def compute_where(expr, courses):
assertKeys(expr, '$where', '$count')
filtered = filterByWhereClause(courses, expr['$where'])
expr['_matches'] = filtered
return len(filtered) >= expr['$count']
# Contextual Computes:
# boolean, modifier, of, reference
def compute_boolean(expr, ctx, courses):
if '$or' in expr:
return any([compute_chunk(req, ctx, courses) for req in expr['$or']])
elif '$and' in expr:
return all([compute_chunk(req, ctx, courses) for req in expr['$and']])
elif '$not' in expr:
return not (compute_chunk(expr['$not'], ctx, courses))
else:
print
print result
raise RequiredKeyException(msg='none of $or, $and, or $not could be found')
def compute_of(expr, ctx, courses):
assertKeys(expr, '$of', '$count')
evaluated = [compute_chunk(req, ctx, courses)
for req in expr['$of']]
truthy = [i for i in evaluated if i]
return len(truthy) >= expr['$count']
def compute_reference(expr, ctx, courses):
assertKeys(expr, '$requirement')
if expr['$requirement'] in ctx:
target = ctx[expr['$requirement']]
return target['computed']
else:
return False
def compute_modifier(expr, ctx, courses):
assertKeys(expr, '$what', '$count', '$from')
what = expr['$what']
if what not in ['course', 'department', 'credit']:
raise UnknownPropertyException(what)
if expr['$from'] == 'children':
return 'not yet implemented'
elif expr['$from'] == 'where':
assertKeys(expr, '$where', '$count')
filtered = filterByWhereClause(courses, expr['$where'])
if what == 'course':
num = countCourses(filtered)
elif what == 'department':
num = countDepartments(filtered)
elif what == 'credit':
num = countCredits(filtered)
return num >= expr['$count']
# And, of course, the function that dispatches the appropriate compute:
def compute_chunk(expr, ctx, courses):
#print
#print 'expression:', expr
#print 'context:', ctx
assertKeys(expr, '$type')
type = expr['$type']
computed = False
if type == 'boolean':
computed = compute_boolean(expr, ctx, courses)
elif type == 'course':
computed = compute_course(expr, courses)
elif type == 'modifier':
computed = compute_modifier(expr, ctx, courses)
elif type == 'occurrence':
computed = compute_occurrence(expr, courses)
elif type == 'of':
computed = compute_of(expr, ctx, courses)
elif type == 'reference':
computed = compute_reference(expr, ctx, courses)
elif type == 'where':
computed = compute_where(expr, courses)
expr['_result'] = computed
return computed
# The overall computation is done by compute, which is in charge of computing sub-requirements and such.
def compute(requirement, path, courses=[], overrides={}):
this_name = path[-1]
#print ''
#print requirement, this_name
requirement = {
name: compute(req, path+[name], courses, overrides)
if isRequirement(name)
else req
for name, req in requirement.items()
}
computed = False
if 'result' in requirement:
computed = compute_chunk(requirement['result'], requirement, courses)
# requirement['result'] = result
elif 'message' in requirement:
computed = False
# show a button to toggle overriding
pass
else:
#raise RequiredKeyException(msg='one of message or result is required')
print('one of message or result is required')
requirement['computed'] = computed
if hasOverride(path, overrides):
requirement['overridden'] = True
requirement['computed'] = getOverride(path, overrides)
return requirement
def main():
assertKeys(data, 'name', 'result', 'type', 'revision')
name = data['name']
type = data['type']
courses = [
{"department": ["ART"], "number": 102},
{"department": ["ART"], "number": 103},
{"department": ["ART"], "number": 104},
{"department": ["ART"], "number": 106},
{"department": ["ART"], "number": 205},
{"department": ["ART"], "number": 207},
{"department": ["ART"], "number": 221},
{"department": ["ART"], "number": 222},
{"department": ["ART"], "number": 223},
{"department": ["ART"], "number": 224},
{"department": ["ART"], "number": 225},
{"department": ["ART"], "number": 226},
{"department": ["ART"], "number": 227},
{"department": ["ART"], "number": 228},
{"department": ["ART"], "number": 229},
{"department": ["ART"], "number": 232},
{"department": ["ART"], "number": 233},
{"department": ["ART"], "number": 234},
{"department": ["ART"], "number": 236},
{"department": ["ART"], "number": 238},
{"department": ["ART"], "number": 343},
{'department': ['ART'], 'number': 398},
{'department': ['ART', 'ASIAN'], 'number': 259},
{'department': ['CSCI'], 'number': 241},
]
pp = pprint.PrettyPrinter(width=40, indent=2)
c = compute(requirement=data, path=[type, name], courses=courses, overrides=overrides)
#print pprint.pformat(c, indent=2)
print 'outcome:', c['computed']
#print data
def test():
print compareCourseAgainstOperator(
{'department': ['CSCI']},
'department', {'$eq': 'ART'})
if __name__ == "__main__":
#test()
main()
// # hanson.pegjs (take 8)
// # TODO:
// # - ensure that of-statements require commas between items
// # - embed types into expressions
// # - [x] make occurrence return "$course", instead of just "course"
/*
one of (
CHEM 110 & 115,
CSCI 121,
three of (
MATH 130,
one course where {gereq == EIN & year <= max(year) from courses where {gereq = BTS-T}},
two occurrences of THEAT130
)
)
*/
start
= or
expr "expression"
= _ e:(
not
/ parenthetical
/ primitive
/ of
/ modifier
) _
{ return e }
primitive
= _ p:(
course
/ where
/ occurrence
/ requirement
) _
{ return p }
// Primitives
where
= count:counter _ "course""s"? _ "where" _ where:qualifier
{ return {
$type: 'where',
$count: count,
$where: where,
} }
occurrence
= count:counter _ "occurrence""s"? _ "of" _ course:course
{ return {
$type: 'occurrence',
$count: count,
course
} }
// Primitive Components
qualifier
= "{" _ q:or_q _ "}"
{ return q }
// only enable these if it's actually needed
// qualifier
// = "{" _ q:(parenthetical_q / or_q)+ _ "}"
// { return q }
// parenthetical_q
// = "(" _ q:(or_q)+ _ ")"
// { return q }
or_q
= lhs:and_q _ "|" _ rhs:or_q
{ return {$type: "boolean", $or: [lhs].concat('$or' in rhs ? rhs.$or : [rhs])} }
/ and_q
and_q
= lhs:(parenthetical_q / qualification) _ "&" _ rhs:and_q
{ return {$type: "boolean", $and: [lhs].concat('$and' in rhs ? rhs.$and : [rhs])} }
/ parenthetical_q
/ qualification
qualification
= key:word _
op:operator _
value:(
word:[a-z0-9_\-]i+ !("(")
{ return word.join("") }
/ f:func _ "from" _ "courses" _ "where" _ q:qualifier
{ return Object.assign(f, {$where: q}) }
)
{ return {$type: "qualification", $key: key, $value: {[op]: value, $type: "operator"}} }
word
= chars:[a-z]i+
{ return chars.join("") }
func
= name:word _ "(" _ prop:word _ ")"
{ return {$name: name, $prop: prop, $type: "function"} }
operator
= ("<=") { return "$lte" }
/ ("<") { return "$lt" }
/ ("==" / "=") { return "$eq" }
/ (">=") { return "$gte" }
/ (">") { return "$lt" }
/ ("!=") { return "$ne" }
_ "whitespace"
= [ \n\t\r]*
counter
= english_integer
/ numeric_integer
english_integer
= "zero" { return 0 }
/ "one" { return 1 }
/ "two" { return 2 }
/ "three" { return 3 }
/ "four" { return 4 }
/ "five" { return 5 }
/ "six" { return 6 }
/ "seven" { return 7 }
/ "eight" { return 8 }
/ "nine" { return 9 }
/ "ten" { return 10 }
/ "eleven" { return 11 }
/ "twelve" { return 12 }
/ "thirteen" { return 13 }
/ "fourteen" { return 14 }
/ "fifteen" { return 15 }
/ "sixteen" { return 16 }
/ "seventeen" { return 17 }
/ "eighteen" { return 18 }
/ "nineteen" { return 19 }
/ "twenty" { return 20 }
numeric_integer
= num:[0-9]+
{ return parseInt(num.join(""), 10) }
// Expressions
not
= "!" _ value:expr
{ return {$type: 'boolean', $not: value} }
parenthetical
= "(" _ value:start _ ")"
{ return value }
or
= lhs:and _ "|" _ rhs:or
{ return {$type: 'boolean', $or: [lhs].concat('$or' in rhs ? rhs.$or : [rhs])} }
/ and
and
= lhs:expr _ "&" _ rhs:and
{ return {$type: 'boolean', $and: [lhs].concat('$and' in rhs ? rhs.$and : [rhs])} }
/ expr
of
= count:(counter / "all" / "none" { return 0 } / "any" { return 1 }) _ "of" _ "(" _ of:(
val:start
rest:( _ "," _ second:start { return second } )* ","?
{ return [val].concat(rest) }
)+ _ ")"
{
var flattened = Array.prototype.concat.apply([], of)
if (count === "all")
count = flattened.length
if (count && flattened.length < count)
throw new Error(`this of-statement will never succeed; you requested ${count} items, but only input ${flattened.length} options (${JSON.stringify(flattened)}).`)
return {
$type: 'of',
$count: count,
$of: flattened,
}
}
modifier
= count:counter _
what:("course" / "credit" / "department" & ("from" _ "children")) "s"? _
"from" _ from:(
"children" { return { $from: "children"}}
/ "courses" _ "where" _ where:qualifier { return {$from: "where", $where: where} }
)
{ return Object.assign({
$type: 'modifier',
$count: count,
$what: what,
}, from) }
// Course
course
= dept:c_dept? _
num:c_num
info:("." section:c_sect
y_s:("." year:c_year
sem:("." semester:c_sem
{ return {semester} } )?
{ return Object.assign(sem || {}, {year}) } )?
{ return Object.assign(y_s || {}, {section}) } )?
{ return Object.assign({$type: 'course'},
Object.assign(info || {},
Object.assign(dept || window.lastDept || {}, num))) }
c_dept
= dept1:(c1:[A-Z] c2:[A-Z] { return c1 + c2 })
part2:(
chars:[A-Z]+ { return {dept: chars.join(''), type: "join"} }
/ "/" l1:[A-Z] l2:[A-Z] { return {dept: l1 + l2, type: "seperate"} }
)
{
var {type, dept: dept2} = part2
var department
if (type === "join")
dept = {department: [dept1 + dept2]}
else if (type === "seperate")
dept = {department: [dept1, dept2]}
window.lastDept = dept
return dept
}
c_num "course number"
= p1:[0-9]
p2:(n2:[0-9] n3:[0-9] {return n2 + n3} / "XX")
international:"I"?
_ lab:"L"?
{
var number = undefined
var level = undefined
var result = {}
if (p2 === "XX") {
result.level = parseInt(p1) * 100
}
else {
result.number = parseInt(p1 + p2)
}
if (international)
result.international = true
if (lab)
result.lab = true
return result
}
c_sect
= [A-Z*]
c_year
= nums:([0-9][0-9][0-9][0-9])
{ return nums.join("") }
/ "*"
c_sem
= [1-5*]
requirement
= title:(initial:[A-Z0-9] rest:[0-9a-z\- ]i+
{ return initial + rest.join("") } )
{ return { $type: 'requirement', $requirement: title.trim() } }
## Studio Art Major
Studio Art:
Studio Art Courses:
eight of ( ART 102, 103, 104, 106, 205, 207, 221, 222, 223, 224, 225, 226, 227, 228, 229, 232, 233, 234, 236, 238, 240, 246, 294, 298, 340, 343, 394, 398 )
Foundations:
all of (ART 102, 103, 104)
2D Media:
Drawing: ART 225 | 232 | 233
Painting: ART 221 | 222
Printmaking: ART 226 | 227
result: any of (Drawing, Painting, Printmaking)
3D Media:
Ceramics: ART 207 | 234
Sculpture: ART 223 | 224
result: any of (Ceramics, Sculpture)
New Media:
Photography: ART 205 | 238
Interactive Image: ART 228
Digital Video: ART 229
Performance: ART 240
Graphic Design: ART 236
result: any of ( Photography, Interactive Image, Digital Video, Performance, Graphic Design )
Juried Show:
message: To fulfill the requirements of the Studio Art major, you must enter at least two juried art exhibitions on or off campus by the beginning of your senior year.
Senior Thing: ART 343
result: all of (Foundations, 2D Media, 3D Media, New Media, Juried Show, Senior Thing, Studio Art Courses)
Art History:
message: The department strongly recommends that you take ART 252 or 253 as one of your art history courses.
result: two of (ART 153, 161, 251, 252, 253, 254, 255, 256, 263, 269, 271, 275, 277, 280, 294, 298, 350, 379, 394, 396, 398, AR/AS 259, 260, 262, 270, 310, ENVST 270, PHIL 243, RACE 253 )
Art Credits:
ten credits from courses where {department=ART}
title: Studio Art
type: Major
revision: 2014-15
result: Studio Art & Art History & Art Credits
@mivera-maxi
Copy link

If you are looking for an online casino that guarantees safe and secure gambling, then https://www.aucasinoslist.com/ for players from Australia can be extremely helpful. Use welcome bonuses to get started and enjoy an exciting gaming experience without any worries. Only verified casinos are collected here for your convenience. Among them, you can choose any one and start your immersion into the world of gambling.

@antonohuan
Copy link

Se quiseres dominar o jogo Aviador e ter sucesso, não deixes de visitar o casinoaviator.net ! Este site é um verdadeiro tesouro de conhecimento sobre como jogar Aviator no Brasil. Os guias detalhados, estratégias e dicas ajudam a entender melhor o jogo e a melhorar suas habilidades. Fiquei muito satisfeito com o facto de todos os materiais serem apresentados numa linguagem simples e compreensível. Graças a este recurso, tive muito mais sucesso nas minhas apostas.

@azarienko
Copy link

Zacząłem interesować się sportem i pomyślałem, że zakłady mogą być ciekawym sposobem na urozmaicenie oglądania meczów. Chciałem znaleźć niezawodną stronę z bogatą ofertą zakładów sportowych, więc odwiedziłem mostbet-pln. Strona oferuje szeroki wybór dyscyplin sportowych, a także różne opcje zakładów, co sprawia, że każdy znajdzie coś dla siebie. Dodatkowo, strona ma wiele atrakcyjnych bonusów, które są idealne dla nowych graczy. Wykorzystałem jeden z bonusów i udało mi się wygrać, co dodatkowo mnie zachęciło do dalszej gry.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment