The resolver converts an abstract syntax tree (AST) into an abstract semantic graph (ASG). Type definitions for the AST, ASG and expression types are found in ast.ts, asg.ts and type.ts respectively. The resolver also performs:

Much of the work is done by the functions in DECL_RESOLVE_FUNCS, EXPR_RESOLVE_FUNCS, RULE_RESOLVE_FUNCS and STMT_RESOLVE_FUNCS. These constant object map each AST node kind to a function which checks and resolves AST nodes of that kind.


The Context class represents the current lexical context, including the declared grids, current grid, symmetry group for patterns, and variables and their types. The class is mutable, and only one instance will be used to resolve an entire AST.

Most changes to the context are temporary; for example, when resolving a variable declaration, the variable will be added to the context for the duration of resolving the part of the AST where that variable is in scope, and the variable is then removed from the context. The methods withOutGrid, withSymmetry, withVariable etc. are used for some temporary context changes.

The main exception to the above is that when a stmt.map or stmt.use statement changes the context’s current grid, the change persists beyond the statement’s own lexical scope. Additionally, when a runtime parameter is declared with a let param declaration, another runtime parameter of the same name cannot later be declared even if the first parameter is no longer in scope.


To avoid excessive code repetition, most expressions contained within declarations, rules or statements are resolved through the _resolveProp and _resolveProps functions. These functions resolve and check an expression according to a PropSpec, which is a string consisting of:

The PROP_SPECS object defines PropSpec strings for resolving some properties of some kinds of AST nodes. These are used by the _resolveProps function.


ASG expressions are additionally labelled with flags indicating some static properties. These are defined in the ExprFlags enum, and have the following meanings:

The purpose of these flags is to allow certain optimisations to be made by the compiler; particularly, to avoid repeatedly evaluating expressions when it is statically-known that the result will not have changed.