Lines
79.93 %
Functions
42.22 %
use super::atom::parse_int;
use super::util::named_children;
use crate::diagnostics::diagnostics_api::SymbolKind;
use crate::diagnostics::source_map::{HoverInfo, span_with_hover};
use crate::errors::FatalParseError;
use crate::expression::parse_expression;
use crate::parser::ParseContext;
use crate::{RecoverableParseError, child};
use conjure_cp_core::ast::{
DeclarationPtr, Domain, DomainPtr, IntVal, Moo, Name, Range, RecordEntry, Reference, SetAttr,
};
use tree_sitter::Node;
use crate::field;
/// Parse an Essence variable domain into its Conjure AST representation.
pub fn parse_domain(
ctx: &mut ParseContext,
domain: Node,
) -> Result<Option<DomainPtr>, FatalParseError> {
match domain.kind() {
"domain" => {
let inner = match domain.child(0) {
Some(node) => node,
None => {
ctx.record_error(RecoverableParseError::new(
format!("{} in expression of kind '{}'", "domain", domain.kind()),
Some(domain.range()),
));
return Ok(None);
}
parse_domain(ctx, inner)
"bool_domain" => {
ctx.add_span_and_doc_hover(&domain, "L_bool", SymbolKind::Domain, None, None);
Ok(Some(Domain::bool()))
"int_domain" => parse_int_domain(ctx, domain),
"identifier" => {
let Some(decl) = get_declaration_ptr_from_identifier(ctx, domain)? else {
let Some(dom) = Domain::reference(decl) else {
ctx.record_error(crate::errors::RecoverableParseError::new(
format!(
"The identifier '{}' is not a valid domain",
&ctx.source_code[domain.start_byte()..domain.end_byte()]
),
let name = &ctx.source_code[domain.start_byte()..domain.end_byte()];
// Not form docs, because we need context specific hover info
span_with_hover(
&domain,
ctx.source_code,
ctx.source_map,
HoverInfo {
description: format!("Domain reference: {name}"),
kind: Some(SymbolKind::Variable),
ty: None,
decl_span: None, // could link to the declaration span if we wanted
},
);
Ok(Some(dom))
"tuple_domain" => parse_tuple_domain(ctx, domain),
"matrix_domain" => parse_matrix_domain(ctx, domain),
"record_domain" => parse_record_domain(ctx, domain),
"set_domain" => parse_set_domain(ctx, domain),
_ => {
format!("{} is not a supported domain type", domain.kind()),
Ok(None)
fn get_declaration_ptr_from_identifier(
identifier: Node,
) -> Result<Option<DeclarationPtr>, FatalParseError> {
let name = Name::user(&ctx.source_code[identifier.start_byte()..identifier.end_byte()]);
let decl = ctx.symbols.as_ref().unwrap().read().lookup(&name);
if decl.is_none() {
format!("The identifier '{}' is not defined", name),
Some(identifier.range()),
match decl {
Some(decl) => Ok(Some(decl)),
/// Parse an integer domain. Can be a single integer or a range.
fn parse_int_domain(
int_domain: Node,
let int_keyword_node = child!(int_domain, 0, "int");
if int_domain.child_count() == 1 {
// for domains of just 'int' with no range
ctx.add_span_and_doc_hover(&int_keyword_node, "L_int", SymbolKind::Domain, None, None);
return Ok(Some(Domain::int(vec![Range::Bounded(i32::MIN, i32::MAX)])));
let Some(range_list) = field!(recover, ctx, int_domain, "ranges") else {
let mut ranges_unresolved: Vec<Range<IntVal>> = Vec::new();
let mut all_resolved = true;
for domain_component in named_children(&range_list) {
match domain_component.kind() {
"atom" | "arithmetic_expr" => {
let Some(int_val) = parse_int_val(ctx, domain_component)? else {
if !matches!(int_val, IntVal::Const(_)) {
all_resolved = false;
ranges_unresolved.push(Range::Single(int_val));
"int_range" => {
let lower_bound = match domain_component.child_by_field_name("lower") {
Some(node) => {
match parse_int_val(ctx, node)? {
Some(val) => Some(val),
None => return Ok(None), // semantic error occurred
None => None,
let upper_bound = match domain_component.child_by_field_name("upper") {
match (lower_bound, upper_bound) {
(Some(lower), Some(upper)) => {
// Check if both bounds are constants and validate lower <= upper
if let (IntVal::Const(l), IntVal::Const(u)) = (&lower, &upper) {
if l > u {
"Invalid integer range: lower bound {} is greater than upper bound {}",
l, u
Some(domain_component.range()),
} else {
ranges_unresolved.push(Range::Bounded(lower, upper));
(Some(lower), None) => {
if !matches!(lower, IntVal::Const(_)) {
ranges_unresolved.push(Range::UnboundedR(lower));
(None, Some(upper)) => {
if !matches!(upper, IntVal::Const(_)) {
ranges_unresolved.push(Range::UnboundedL(upper));
"Invalid int range: must have at least a lower or upper bound"
.to_string(),
"Unexpected int domain component: {}",
domain_component.kind()
// If all values are resolved constants, convert IntVals to raw integers
if all_resolved {
let ranges: Vec<Range<i32>> = ranges_unresolved
.into_iter()
.map(|r| match r {
Range::Single(IntVal::Const(v)) => Range::Single(v),
Range::Bounded(IntVal::Const(l), IntVal::Const(u)) => Range::Bounded(l, u),
Range::UnboundedR(IntVal::Const(l)) => Range::UnboundedR(l),
Range::UnboundedL(IntVal::Const(u)) => Range::UnboundedL(u),
Range::Unbounded => Range::Unbounded,
_ => unreachable!("all_resolved should be true only if all are Const"),
})
.collect();
Ok(Some(Domain::int(ranges)))
// Otherwise, keep as an expression-based domain
// Adding int keyword to the source map with hover info from documentation
Ok(Some(Domain::int(ranges_unresolved)))
// Helper function to parse a node into an IntVal
// Handles constants, references, and arbitrary expressions
fn parse_int_val(ctx: &mut ParseContext, node: Node) -> Result<Option<IntVal>, FatalParseError> {
// For atoms, try to parse as a constant integer first
if node.kind() == "atom" {
let text = &ctx.source_code[node.start_byte()..node.end_byte()];
if let Ok(integer) = text.parse::<i32>() {
return Ok(Some(IntVal::Const(integer)));
// Otherwise, check if it's an identifier reference
let Some(decl) = get_declaration_ptr_from_identifier(ctx, node)? else {
// If identifier isn't defined, its a semantic error
return Ok(Some(IntVal::Reference(Reference::new(decl))));
// For anything else, parse as an expression
let Some(expr) = parse_expression(ctx, node)? else {
Ok(Some(IntVal::Expr(Moo::new(expr))))
fn parse_tuple_domain(
tuple_domain: Node,
let mut domains: Vec<DomainPtr> = Vec::new();
for domain in named_children(&tuple_domain) {
let Some(parsed_domain) = parse_domain(ctx, domain)? else {
domains.push(parsed_domain);
// extract the first child node which should be the 'tuple' keyword for hover info
if let Some(first) = tuple_domain.child(0)
&& first.kind() == "tuple"
{
// Adding tuple to the source map with hover info from documentation
ctx.add_span_and_doc_hover(&first, "L_tuple", SymbolKind::Domain, None, None);
Ok(Some(Domain::tuple(domains)))
fn parse_matrix_domain(
matrix_domain: Node,
let Some(index_domain_list) = field!(recover, ctx, matrix_domain, "index_domain_list") else {
for domain in named_children(&index_domain_list) {
let Some(value_domain_node) = field!(recover, ctx, matrix_domain, "value_domain") else {
let Some(value_domain) = parse_domain(ctx, value_domain_node)? else {
// Adding matrix to the source map with hover info from documentation
let matrix_keyword_node = child!(matrix_domain, 0, "matrix");
ctx.add_span_and_doc_hover(
&matrix_keyword_node,
"matrix",
SymbolKind::Domain,
None,
Ok(Some(Domain::matrix(value_domain, domains)))
fn parse_record_domain(
record_domain: Node,
let mut record_entries: Vec<RecordEntry> = Vec::new();
for record_entry in named_children(&record_domain) {
let Some(name_node) = field!(recover, ctx, record_entry, "name") else {
let name = Name::user(&ctx.source_code[name_node.start_byte()..name_node.end_byte()]);
let Some(domain_node) = field!(recover, ctx, record_entry, "domain") else {
let Some(domain) = parse_domain(ctx, domain_node)? else {
record_entries.push(RecordEntry { name, domain });
// Adding record keyword to the source map with hover info from documentation
let record_keyword_node = child!(record_domain, 0, "record");
&record_keyword_node,
"L_record",
Ok(Some(Domain::record(record_entries)))
pub fn parse_set_domain(
set_domain: Node,
let mut set_attribute: Option<SetAttr> = None;
let mut value_domain: Option<DomainPtr> = None;
for child in named_children(&set_domain) {
match child.kind() {
"set_attributes" => {
// Check if we have both minSize and maxSize (minMax case)
let min_value_node = child.child_by_field_name("min_value");
let max_value_node = child.child_by_field_name("max_value");
let size_value_node = child.child_by_field_name("size_value");
if let (Some(min_node), Some(max_node)) = (min_value_node, max_value_node) {
// MinMax case
let Some(min_val) = parse_int(ctx, &min_node) else {
let Some(max_val) = parse_int(ctx, &max_node) else {
set_attribute = Some(SetAttr::new_min_max_size(min_val, max_val));
} else if let Some(size_node) = size_value_node {
// Size case
let Some(size_val) = parse_int(ctx, &size_node) else {
set_attribute = Some(SetAttr::new_size(size_val));
} else if let Some(min_node) = min_value_node {
// MinSize only case
set_attribute = Some(SetAttr::new_min_size(min_val));
} else if let Some(max_node) = max_value_node {
// MaxSize only case
set_attribute = Some(SetAttr::new_max_size(max_val));
let Some(parsed_domain) = parse_domain(ctx, child)? else {
value_domain = Some(parsed_domain);
format!("Unrecognized set domain child kind: {}", child.kind()),
Some(child.range()),
if let Some(domain) = value_domain {
// Adding set to the source map with hover info from documentation
let set_keyword_node = child!(set_domain, 0, "set");
// No documentation available for set domain, using fallback description
ctx.add_span_and_doc_hover(&set_keyword_node, "set", SymbolKind::Domain, None, None);
Ok(Some(Domain::set(set_attribute.unwrap_or_default(), domain)))
"Set domain must have a value domain".to_string(),
Some(set_domain.range()),