Skip to content

Prerequisite Expression Language

The Prerequisite Expression Language is used to logically encode the requirements for taking a course, or for completing a degree, major, specialisation, etc.

This document provides a comprehensive guide to the language syntax, structure, and evaluation model.

📚 Overview

  • Basic Constructs
    Learn how to write simple requirements using course codes, unit counts, and wildcards.

  • General Notes on Evaluation
    Understand how the evaluator matches courses against expressions and why ordering matters.

  • Advanced Constructs
    Explore filters, non-consuming checks (WEAK), multi-clause expressions (UNITS), and more.

  • Examples
    See real-world input → output conversions to understand the language in practice.

  • EBNF Grammar
    View the full (approximate) formal grammar of the language for reference.


Basic Constructs

  • COMP1100 — the default number of units of a certain course must be taken prior to now
  • ~COMP1100 — the default number of units of a certain course that are taken concurrently
  • 6 * <COMP1100 | COMP1130> — units from a group of courses (any of them can count)
  • 12 * <['MATH_']> — units from a group of courses, using a wildcard (this one is 'any MATH course')
  • 12 * <COMP1100 | ENGN4213 | ['MATH_'] | ['_3']> — units from a group of courses, mixed (any combinations are fine)
  • COMP1100 & COMP1730 - 'AND' expression (the left-hand-side and right-hand-side must be satisfied)
  • COMP1100 | COMP1730 - 'OR' expression (the left-hand-side or right-hand-side must be satisfied)

These can be mixed and matched using brackets and such, too. e.g.:

COMP1100 & COMP1110 & (MATH1005 | MATH2222) & 24 * <['COMP3_'] | ['COMP4_'] | ENGN4213>

This means: 'must take COMP1100 and COMP1110 and (either MATH1005 or MATH2222), and 24 units of (3000 or 4000-level COMP, or ENGN4213).

For instance this might be satisfied by taking:

  • COMP1100, COMP1110, MATH1005, COMP3500 (12 units), ENGN4213, COMP4600

Or, maybe like this!

  • COMP1100, COMP1110, MATH2222, COMP3600, COMP4600, COMP4670, COMP3900

Basic Wildcards

These must appear in square brackets and quotes inside a 'group' expression (the X * < ... > syntax above).

Wildcard Description
['_'] any course
['_3'] any course at a given level (e.g. 'any 3000-level course')
['MATH_'] any course of a certain subject (e.g. 'any MATH course')
['MATH3_'] any course at a given level, in a certain subject (e.g. 'any 3000-level MATH course')

General Notes on Evaluation

Before we get into the exact syntax, we need to look at how the planner evaluates expressions. Take the following:

MATH1005 & 6 * <COMP1100 | ['MATH_']>

This expression means that someone must have completed MATH1005, and then either COMP1100, or any MATH course.

Now we consider whether this expression would be satisfied if the user just took MATH1005. We find that it isn't, and this is because the evaluator does not double count courses to requirements. MATH1005 could count toward its explicit mention in the expression, or against the ['MATH_'] wildcard, but it can't count for both.

Now we consider someone who has taken both COMP1100 and MATH1005. We can see this is clearly satisfied: the left-hand-side of the & is satisfied by taking MATH1005, and the right-hand-side by taking COMP1100.

But if we were to evaluate this 'wrongly', we can find ways for this to not be satisfied. For instance, let's evaluate the right-hand-side first. We'll match the MATH1005 the user has taken against the ['MATH_'] wildcard (instead of COMP1100 against its explicit requirement). Now when we get to the left-hand-side, we are stuck.

Important Takeaway

Courses a user have taken can only be matched against a single requirement, they do not double count.

The evaluator will test all combinations of ways it can get the courses you have taken to match against the requirements, as it is sensitive to evaluation order and which courses match against which wildcards. If any combination can match, then the requirement is satisfied.

But Actually...

This is a slight simplification. Courses can have various unit amounts, and they can count as long as there are units left.

e.g. if a course COMP4500 if worth 12 units, and there is a requirement such as: 6 * <['COMP_']> & 6 * <['COMP4_']> (i.e. 6 units of any COMP course, then 6 units of any 4000-level COMP course), it will match, as the 12 units can be split across both requirements. It will also match the requirement COMP4500 & 6 * <['COMP4_']>, as a raw course name (COMP4500) counts for the default number of units (6 in this case - see Degree Configuration Files for how to set this). If you have courses that have unit configurations different to the default, it is highly recommended to use the unit syntax (12 * <COMP4500>).


Advanced Constructs

Corequisite/Concurrent Expressions

Regular course requirements check that a course has been taken prior to the the student taking the course in question. However, sometimes it is useful to check if a given course is being taken at the same time instead. This is called a corequisite.

We can check for this with the tilde (~) syntax, e.g. ~COMP1130 checks if COMP1130 is being taken at the same time as the course in question (and only the present time).

If it may be taken as a prerequisite or corequisite, you need to specify both, i.e: (COMP1130 | ~COMP1130)

Entire wildcards may be marked as concurrent, e.g. 12 * <['COMP4_'] | ~['COMP4_']> is valid. The tilde (~) may go outside or inside the square brackets. e.g. both ~['ENGN_'] and [~'ENGN_'] are valid.

The ~ must only be used with raw course codes or wildcards. It cannot go in front of an arbitrary expression.

Negation Expressions

Many courses are incompatible with other courses, for instance if there is an regular and advanced version of the same course, or a postgraduate and undergradaute version, and it is inpermissible to take both courses. This is not generally dealt with by the prerequsite expression. A seperate field in the course configuration specifies a list of incompatible courses.

There are occasionally times where more complex incompatibility logic is required, where it is conditional. There are two forms of this:

  • A negation expression can be used to check that a course has not been taken. e.g. !COMP1130
  • A negation expression can form part of a group, to exclude particular courses from a wildcard. e.g. 12 * <['COMP4_'] | !COMP4500 | !COMP4820> requires you to take 12 units from any 4000-level COMP courses except for COMP4500 and COMP4820.

The positioning of the courses within the group does not matter (i.e. it does not matter if negations appear before or after wildcards that mention that course) - the courses marked with ! will be removed after all courses have been collected in the group.

The ! must only go before a raw course code. It cannot go in front of an arbitrary expression, nor a wildcard.

Complex Wildcards

Additional, more advanced wildcards exist:

Wildcard Description
['LAWS61_'] any course in a certain subject, with the given level and sublevel
['TRANSDISCIPLINARY'] (ANU specific) any course marked as having the transdisciplinary problem solving attribute

Permission Code Expressions

Some courses require the permission of a convener before you can enrol. Others require placement tests, finding a research supervisor and topic approval, the finding of an internship, etc.

In these cases the PC (permission code) expression can be used to mark that there is a further external requirement that cannot be expressed in the degree planner.

For instance a course which says "you must comple 96 units of courses, have a WAM of 75 or greater, completed ENGN3300 and ENGN3301, and be approved to enrol" might look like this: WEAK(96 & <1 ['_']>) & WAM >= 75 & ENGN3300 & ENGN3301 & PC.

Optionally, a string literal may be given afterward to explain to the user the requirement. e.g. (JPNS2003 & JPNS2005) | PC "have completed a language proficiency assessment".

Other Expressions

An alternative method of expressing something not otherwise expressible in the language is to use an OTHER expression. This takes a string after it, and is passed to the uni-specific part of the backend, for it to do whatever it likes with. This ensures the language can be extended for any particularly unique rules.

e.g. 24 * <['_']> & OTHER "CBE_INTERNSHIP" expresses that 24 units of any course must be taken, and then some uni specific check regarding a certain internship will also be run. These uni-specific functions must be written into the backend before the corresponding OTHER expression will work.

In general, if requirements are getting this complicated, it is not always worth including it, and therefore a PC expression is likely enough to warn the user something more is going on.

WAM Expressions

Some courses may require that a student's weighted average mark across their degree is above a certain value (e.g. in order to undertake an advanced course, an honours project, internship, etc).

The syntax WAM >= 50 can be used. Only integral WAM values are supported, with values between 0 and 100 inclusive.

The planner internally supports allocating a grade to each completed course, but this in no longer used. Requiring grade entry adds unnecessary complexity for students to use the planner, and they are instead just presented with a warning on the course sidebar that they must check their WAM meets the requirement before enrolling in the course.

GPA Expressions

Similar to the WAM expression, a student's GPA (grade point average) can be tested against. e.g. GPA >= 5.

It may be useful to be able to specify decimals, e.g. to check if the GPA exceeeds 5.5. As the language tokeniser only supports integers, we instead specify this as follows: GPA >= 55. Two digit GPA values are internally divided by 10 before the comparison is performed. This means GPAs must be in the range of 0.0 to 9.9, in increments of 0.1.

Grade Expressions

...

Degree Expressions

...

Year Expressions

...

Then Expressions

...

After Expressions

...

Constant Expressions

The expressions FALSE and TRUE evaluate to those constant values. They are only rarely useful, and probably only useful in debugging.

e.g.

  • FALSE (this prerequisite can never be met)
  • TRUE (this prerequisite is always met - this is the same as having an empty prerequsite expression)

Weak Expressions

...

Hint Expressions

...

Fast-Path Groups

In the evaluation of group expressions (X * <...>), the evaluator will attempt all combinations to get courses to match wildcards. Is is to ensure the later parts of the expression can be tested against all combinations, to ensure we test all possibilities.

However, in some cases, we do not need this, and want to just stop as soon as we find a single match. This can significantly speed up evaluation times.

This is best done where there is either a wildcard on its own, or a wildcard which no other course can match within its enclosing scope (e.g. something in a WEAK block doesn't effect the outside of it).

We call tell the evaluator to stop at the first match by using the following form:

X * <1 ...>

For instance:

  • 48 * <1 ['_']>
    • Matches any 48 units of courses
    • This works because there are no further clauses in the expression that rely on the result.
  • COMP1100 & 24 * <1 ['ENGN_']>
    • Matches COMP1100 and 24 units of ENGN courses
    • This works because there is no overlap between COMP1100 and ENGN courses
  • 30 * <1 ['_2'] | ['_3']> & WEAK(96 * <1 ['_']>)
    • Matches "96 units of any course, and independently, 30 units must come from 2000 or 3000 level courses"
    • This works because the 96 unit requirement is matched weak, and therefore anything outside it doesn't affect the inside and vice versa

All of these examples would still work without the <1 syntax, but would be significantly slower to evaluate.

Filter Expressions

...

Unit Expressions

...

Subst Expressions

...

Select Expressions

...


Examples

Below is a list of real-world examples of course logic in English and their equivalent prerequisite expressions.


Whitespace and Precedence

Input (English):

COMP3670

OR

COMP1110 OR COMP1140
AND
MATH1014 OR MATH1115 OR MATH1116

Expression:

COMP3670 | ((COMP1110 | COMP1140) & (MATH1014 | MATH1115 | MATH1116))

Basic Unit Conversion

Input (English):

You must have completed 72 units toward a degree including BIOL1004.
Incompatible with BIOL1123.

Expression:

66 * <['_']> & BIOL1004

Weak Check Alternative

Same as above, using a WEAK clause:

Expression:

72 * <['_']> & WEAK(BIOL1004)

Advanced Filter Use

Input (English):

Must complete 24 units of some list of courses, and 24 units from some other list of courses,
but between the two, you must take 18 units of COMP3xxx unless you take COMP4600.

Expression:

FILTER(18 * <['COMP3_']> | COMP4600) {
    24 * <...> & 24 * <...>
}

Filter + Weak Completion Check

Input (English):

Must have completed 96 units toward a degree which must include:
30 units of courses at 2000/3000 level, Credit average in ENVS courses,
and Distinction in proposed research project.

Expression:

30 * <1 ['_2'] | ['_3']> & PC & WEAK(96 * <1 ['_']>)

Use of SUBST

Input (English):

Completion of one of the following computing majors:
COMS-MAJ, CSEC-MAJ, DTSC-MAJ, HCCC-MAJ

Expression:

SUBST("COMS-MAJ", "CSEC-MAJ", "DTSC-MAJ", "HCCC-MAJ")

Grade-Based Entry

Input (English):

Students who completed MATH1116 or MATH1113 with ≥60,
or MATH1013/MATH1014 with ≥80.

Expression:

MATH1116 >= 60 | MATH1113 >= 60 | MATH1013 >= 80 | MATH1014 >= 80

Year-Dependent Path

Input (English):

ACT Specialist Maths double major students may take this in first year
concurrently with MATH1115.

Expression:

(~MATH1115 & YEAR 1) | (MATH1116 >= 60 | MATH1113 >= 60 | MATH1013 >= 80 | MATH1014 >= 80)

Degree-Specific with Level-Based Logic

Input (English):

Enrolled in Bachelor of Laws (ALLB) and 5 LAWS 1000-level courses which may be concurrent,
OR Juris Doctor and 5 LAWS 1000/6100-level courses which may be concurrent.

Expression:

(DEG "Bachelor of Laws (ALLB)" & 30 * <['LAWS1_'] | [~'LAWS1_']>)
| (DEG "Juris Doctor (MJD)" & 30 * <['LAWS1_'] | [~'LAWS1_'] | ['LAWS61_'] | [~'LAWS61_']>)

Concurrency Logic

Input (English):

Must have completed or be concurrently enrolled in EMET8005 and ECON8013.
Excludes COMP1140.

Expression:

(EMET8005 | ~EMET8005) & (ECON8013 | ~ECON8013)

Use of UNITS and FILTER Together

Input (English):

48 units, which must consist of at least 18 units of COMP3xxx courses, made up of:

  • 12 units from COMP3900 and COMP1720
  • at least 12 units from COMP3540 | COMP4350 | COMP4610 | COMP4528
  • at most 12 units from COMP1710 | HUMN1001 | MUSI1110 | PHIL1008
  • at most 24 units from ARTH2181 | ARTV2059 | COMP2120 | COMP3670 | DESN2004 | DESN2008 | DESN2010 | HUMN2001 | MGMT2009 | MUSI3309 | SCOR3001 | SOCY2038 | SOCY2166

Expression:

COMP1720 & COMP3900 & FILTER(12 * <['COMP3_']>) {
    UNITS 36 {
        MIN 12 * <COMP3540 | COMP4350 | COMP4610 | COMP4528>
        MAX 12 * <COMP1710 | HUMN1001 | MUSI1110 | PHIL1008>
        MAX 24 * <ARTH2181 | ARTV2059 | COMP2120 | COMP3670 | DESN2004 | DESN2008 | DESN2010 | HUMN2001 | MGMT2009 | MUSI3309 | SCOR3001 | SOCY2038 | SOCY2166>
    }
}

Explanatory Notes

COMP3900 and COMP1720 are required, and so get excluded from the UNITS block. Between them, they make up 12 units, so this 12 is removed from the 48 required, to give a UNITS 36 { ... }. We additionally lower the 18 units of COMP3xxx down to 12 units, because we are required to take COMP3900, which counts for 6 units.

EBNF

This is not guaranteed to be correct, but just a sketch that may be of use.

<expression>        ::= <or-expr> ;

<or-expr>           ::= <and-expr>
                      | <or-expr> "|" <and-expr> ;

<and-expr>          ::= <term>
                      | <and-expr> "&" <term> ;

<term>              ::= <parenthesised>
                      | <unit-expr>
                      | <special-expr>
                      | <course-term>
                      | <group-expr> ;

<parenthesised>     ::= "(" <expression> ")" ;

<course-term>       ::= [ "!" ] [ "~" ] <COURSE_CODE>
                      | [ "!" ] [ "~" ] "[" [ "~" ] <STRING_LITERAL> "]" ;

<group-expr>        ::= "<" [ "1" ] <group-item> { "|" <group-item> } ">" [ ">=" <INTEGER> ] ;

<group-item>        ::= <course-term> ; 

<unit-expr>         ::= <units-prefix> <course-or-group>
                      | <courses-default-units> ;

<units-prefix>      ::= <INTEGER> "*" ; 

<course-or-group>   ::= <course-term> | <group-expr> ;

<courses-default-units>
                    ::= <course-term> ; 

<units-block>       ::= "UNITS" <INTEGER> "{" <minmax-clause>{ <minmax-clause> } "}" ;

<minmax-clause>     ::= ( "MIN" | "MAX" ) <INTEGER> "*" <group-expr> ;

<filter-expr>       ::= "FILTER" "(" <expression> ")" "{" <expression> "}" ;

<hint-expr>         ::= "HINT" "(" <expression> ")" ;

<weak-expr>         ::= "WEAK" "(" <expression> ")" ;

<then-after-expr>   ::= ( "THEN" | "AFTER" ) <COURSE_CODE> [ "YEAR" <INTEGER> ] [ <STRING_LITERAL> ] 

<grade-expr>        ::= <COURSE_CODE> ">=" <INTEGER> ;

<gpa-expr>          ::= "GPA" ">=" <INTEGER> ;

<wam-expr>          ::= "WAM" ">=" <INTEGER> ;

<year-expr>         ::= "YEAR" <INTEGER> [ "+" ] ;

<degree-expr>       ::= "DEG" <STRING_LITERAL> ;

<permission-expr>   ::= "PC" [ <STRING_LITERAL> ] ;

<subst-expr>        ::= "SUBST" <string-list> ;

<select-expr>       ::= "SELECT" <STRING_LITERAL> <string-list> ;  

<string-list>       ::= <STRING_LITERAL> { "," <STRING_LITERAL> } ;

<constant>          ::= "TRUE" | "FALSE" ;

<other-expr>        ::= "OTHER" <STRING_LITERAL> ; 

<special-expr>      ::= <units-block>
                      | <filter-expr>
                      | <weak-expr>
                      | <hint-expr>
                      | <then-after-expr>
                      | <grade-expr>
                      | <gpa-expr>
                      | <wam-expr>
                      | <year-expr>
                      | <degree-expr>
                      | <permission-expr>
                      | <subst-expr>
                      | <select-expr>
                      | <constant>
                      | <other-expr> ;

<COURSE_CODE>       ::= /[A-Z]{4}\d{4}/ ;
<STRING_LITERAL>    ::= '"' ... '"'     
<INTEGER>           ::= /0|[1-9]\d*/ ;