use crate::{formatter::FormatOptions, source::TextRange};
pub use zirql::NativeSetOperator as SetOperator;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Program {
pub functions: Vec<super::FunctionDefinition>,
pub parameters: Vec<zirql::ParameterDeclaration>,
pub(crate) parameter_positions: Vec<usize>,
pub statements: Vec<Statement>,
pub(crate) expression: Expression,
pub(crate) emit_final_result: bool,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Statement {
Binding {
name: String,
expression: Expression,
},
PatternBinding {
names: Vec<zirql::Located<String>>,
expression: Expression,
},
Do(Expression),
Query(Expression),
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum PrintPart {
Literal(String),
Binding(String),
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum JsonLiteral {
Object(Vec<(Vec<PrintPart>, JsonLiteral)>),
Array(Vec<JsonLiteral>),
String(Vec<PrintPart>),
Scalar(serde_json::Value),
Binding(String),
}
impl Program {
pub fn expression(&self) -> &Expression {
&self.expression
}
pub fn range(&self) -> TextRange {
self.expression.range
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Expression {
pub first: Vec<Stage>,
pub rest: Vec<(SetOperator, Vec<Stage>)>,
pub range: TextRange,
}
impl Expression {
pub(crate) fn is_single_closure(&self) -> bool {
self.rest.is_empty()
&& match self.first.as_slice() {
[Stage::Closure { .. }] => true,
[Stage::Group { expression, .. }] => expression.is_single_closure(),
_ => false,
}
}
pub(crate) fn is_map_value(&self) -> bool {
self.rest.is_empty()
&& match self.first.as_slice() {
[Stage::Value { .. }] => true,
[Stage::Group { expression, .. }] => expression.is_map_value(),
_ => false,
}
}
pub fn is_selection_only(&self) -> bool {
self.first_non_selection_stage().is_none()
}
pub(crate) fn first_non_selection_stage(&self) -> Option<&Stage> {
self.first
.iter()
.chain(self.rest.iter().flat_map(|(_, stages)| stages))
.find_map(Stage::first_non_selection_stage)
}
pub(crate) fn is_read_only(&self) -> bool {
self.first
.iter()
.chain(self.rest.iter().flat_map(|(_, stages)| stages))
.all(Stage::is_read_only)
}
pub(crate) fn ends_with_emission(&self) -> bool {
self.rest.is_empty()
&& self.first.last().is_some_and(|stage| match stage {
Stage::Emit { .. }
| Stage::Format { .. }
| Stage::Json { .. }
| Stage::Markdown { .. }
| Stage::Print { .. } => true,
Stage::Group { expression, .. } => expression.ends_with_emission(),
_ => false,
})
}
pub(crate) fn is_terminal(&self) -> bool {
self.first.last().is_some_and(Stage::is_terminal)
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Predicate {
Expression {
value: super::comprehension::ParsedComprehension,
range: TextRange,
},
SameValue {
left: super::capture::ParsedOperand,
right: super::capture::ParsedOperand,
range: TextRange,
},
Bool {
value: bool,
range: TextRange,
},
Computed {
value: ComputedPredicate,
range: TextRange,
},
OpValue {
name: super::comprehension::ParsedComprehension,
range: TextRange,
},
Op {
name: String,
range: TextRange,
},
Dialect {
name: String,
range: TextRange,
},
ResultType {
spelling: String,
range: TextRange,
},
HasAttr {
name: String,
range: TextRange,
},
Attr {
name: String,
value: String,
range: TextRange,
},
Not {
predicate: Box<Predicate>,
range: TextRange,
},
And {
predicates: Vec<Predicate>,
range: TextRange,
},
Or {
predicates: Vec<Predicate>,
range: TextRange,
},
Group {
predicate: Box<Predicate>,
range: TextRange,
},
}
impl Predicate {
pub fn range(&self) -> TextRange {
match self {
Self::Expression { range, .. }
| Self::SameValue { range, .. }
| Self::Bool { range, .. }
| Self::Op { range, .. }
| Self::OpValue { range, .. }
| Self::Computed { range, .. }
| Self::Dialect { range, .. }
| Self::ResultType { range, .. }
| Self::HasAttr { range, .. }
| Self::Attr { range, .. }
| Self::Not { range, .. }
| Self::And { range, .. }
| Self::Or { range, .. }
| Self::Group { range, .. } => *range,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ComputedPredicate {
Dialect(super::comprehension::ParsedComprehension),
ResultType(super::comprehension::ParsedComprehension),
HasAttr(super::comprehension::ParsedComprehension),
Attr {
name: super::comprehension::ParsedComprehension,
value: super::comprehension::ParsedComprehension,
},
}
impl ComputedPredicate {
pub(super) fn operands(
&self,
) -> impl Iterator<Item = &super::comprehension::ParsedComprehension> {
let values = match self {
Self::Dialect(value) | Self::ResultType(value) | Self::HasAttr(value) => {
[Some(value), None]
}
Self::Attr { name, value } => [Some(name), Some(value)],
};
values.into_iter().flatten()
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ComputedStage {
Head(super::comprehension::ParsedComprehension),
Tail(super::comprehension::ParsedComprehension),
Defs(super::comprehension::ParsedComprehension),
Users(super::comprehension::ParsedComprehension),
Check(super::comprehension::ParsedComprehension),
Attr(super::comprehension::ParsedComprehension),
}
impl ComputedStage {
pub(super) fn operand(&self) -> &super::comprehension::ParsedComprehension {
match self {
Self::Head(value)
| Self::Tail(value)
| Self::Defs(value)
| Self::Users(value)
| Self::Check(value)
| Self::Attr(value) => value,
}
}
pub(super) fn parameter_type(&self) -> zirql::ParameterType {
match self {
Self::Attr(_) => zirql::ParameterType::String,
Self::Head(_) | Self::Tail(_) | Self::Defs(_) | Self::Users(_) | Self::Check(_) => {
zirql::ParameterType::Int
}
}
}
pub(super) fn placeholder(&self, range: TextRange) -> Stage {
match self {
Self::Head(_) => Stage::Head { count: 0, range },
Self::Tail(_) => Stage::Tail { count: 0, range },
Self::Defs(_) => Stage::Defs {
index: Some(0),
range,
},
Self::Users(_) => Stage::Users {
index: Some(0),
range,
},
Self::Check(_) => Stage::Check {
expected: Some(0),
message: None,
range,
},
Self::Attr(_) => Stage::Attr {
name: String::new(),
range,
},
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Stage {
Paths {
value: super::capture::ParsedPaths,
range: TextRange,
},
Match {
value: super::capture::ParsedMatch,
range: TextRange,
},
Native {
value: super::capture::ParsedOperand,
range: TextRange,
},
Operand {
operation: super::capture::ParsedOperand,
index: usize,
range: TextRange,
},
FunctionInput {
value: std::sync::Arc<super::QueryOutput>,
range: TextRange,
},
PatternBinding {
value: super::functions::ParsedBinding,
range: TextRange,
},
FunctionValue {
value: super::comprehension::ParsedComprehension,
range: TextRange,
},
FunctionPrimitive {
primitive: super::functions::Primitive,
argument: super::comprehension::ParsedComprehension,
range: TextRange,
},
Computed {
value: ComputedStage,
range: TextRange,
},
Structured {
step: StructuredStage,
range: TextRange,
},
Comprehension {
value: super::comprehension::ParsedComprehension,
range: TextRange,
},
ConstructedSuffix {
value: super::comprehension::ParsedComprehension,
range: TextRange,
},
TallyContinuation {
range: TextRange,
},
Projection {
value: super::report::ParsedProjection,
range: TextRange,
},
Literal {
value: JsonLiteral,
range: TextRange,
},
Markdown {
range: TextRange,
},
Print {
parts: Vec<PrintPart>,
range: TextRange,
},
Binding {
name: String,
range: TextRange,
},
Tally {
range: TextRange,
},
Value {
range: TextRange,
},
MapBy {
key: Box<Expression>,
value: Box<Expression>,
range: TextRange,
},
NativeMap {
mapping: zirql::NativeMap<super::comprehension::ParsedComprehension>,
range: TextRange,
},
Reachable {
range: TextRange,
},
Input {
range: TextRange,
},
Filter {
predicate: Predicate,
range: TextRange,
constructed: Option<super::comprehension::ParsedComprehension>,
},
Closure {
range: TextRange,
},
Slice {
range: TextRange,
},
ForwardSlice {
range: TextRange,
},
Defs {
index: Option<usize>,
range: TextRange,
},
Users {
index: Option<usize>,
range: TextRange,
},
Parent {
range: TextRange,
},
Children {
range: TextRange,
},
Root {
predicate: Predicate,
range: TextRange,
},
Subtree {
range: TextRange,
},
Unique {
range: TextRange,
},
Sort {
range: TextRange,
},
SortBy {
selector: super::report::OrderingSelector,
range: TextRange,
},
RetainSelector {
range: TextRange,
},
Reverse {
range: TextRange,
},
Head {
count: usize,
range: TextRange,
},
Tail {
count: usize,
range: TextRange,
},
Min {
range: TextRange,
},
MinBy {
selector: super::report::OrderingSelector,
range: TextRange,
},
MinAll {
range: TextRange,
},
MinAllBy {
selector: super::report::OrderingSelector,
range: TextRange,
},
Max {
range: TextRange,
},
MaxBy {
selector: super::report::OrderingSelector,
range: TextRange,
},
MaxAll {
range: TextRange,
},
MaxAllBy {
selector: super::report::OrderingSelector,
range: TextRange,
},
Attr {
name: String,
range: TextRange,
},
Names {
range: TextRange,
},
ResultTypes {
range: TextRange,
},
OperandTypes {
range: TextRange,
},
Group {
expression: Box<Expression>,
range: TextRange,
},
WhereExists {
relation: super::comprehension::ParsedComprehension,
range: TextRange,
},
Fixpoint {
expression: FixedPointBody,
range: TextRange,
},
SetAttr {
name: String,
value: String,
range: TextRange,
},
RemoveAttr {
name: String,
range: TextRange,
},
Check {
expected: Option<usize>,
message: Option<String>,
range: TextRange,
},
Count {
range: TextRange,
},
Emit {
range: TextRange,
},
Format {
options: FormatOptions,
range: TextRange,
},
Json {
range: TextRange,
},
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum FixedPointBody {
Selection(Box<Expression>),
Native(super::comprehension::ParsedComprehension),
}
impl FixedPointBody {
pub(super) fn is_single_closure(&self) -> bool {
match self {
Self::Selection(expression) => expression.is_single_closure(),
Self::Native(value) => super::report::fixed_point_is_closure(value),
}
}
fn is_read_only(&self) -> bool {
match self {
Self::Selection(expression) => expression.is_read_only(),
Self::Native(_) => true,
}
}
fn is_selection_only(&self) -> bool {
match self {
Self::Selection(expression) => expression.is_selection_only(),
Self::Native(_) => true,
}
}
}
impl Stage {
pub(super) fn is_read_only(&self) -> bool {
match self {
Stage::SetAttr { .. }
| Stage::RemoveAttr { .. }
| Stage::Emit { .. }
| Stage::Format { .. }
| Stage::Json { .. }
| Stage::Markdown { .. }
| Stage::Print { .. } => false,
Stage::Group { expression, .. } => expression.is_read_only(),
Stage::Fixpoint { expression, .. } => expression.is_read_only(),
Stage::WhereExists { relation, .. } => {
super::report::native_body_is_read_only(relation)
}
Stage::SortBy { selector, .. }
| Stage::MinBy { selector, .. }
| Stage::MinAllBy { selector, .. }
| Stage::MaxBy { selector, .. }
| Stage::MaxAllBy { selector, .. } => selector.is_read_only(),
Stage::NativeMap { mapping, .. } => super::report::map_is_read_only(mapping),
Stage::MapBy { key, value, .. } => key.is_read_only() && value.is_read_only(),
_ => true,
}
}
pub fn range(&self) -> TextRange {
match self {
Self::Paths { range, .. }
| Self::Match { range, .. }
| Self::Native { range, .. }
| Self::Operand { range, .. }
| Self::FunctionInput { range, .. }
| Self::FunctionValue { range, .. }
| Self::PatternBinding { range, .. }
| Self::FunctionPrimitive { range, .. }
| Self::Computed { range, .. }
| Self::Projection { range, .. }
| Self::Comprehension { range, .. }
| Self::ConstructedSuffix { range, .. }
| Self::TallyContinuation { range }
| Self::Structured { range, .. }
| Self::Literal { range, .. }
| Self::Markdown { range }
| Self::Print { range, .. }
| Self::Binding { range, .. }
| Self::Tally { range }
| Self::Value { range }
| Self::NativeMap { range, .. }
| Self::MapBy { range, .. }
| Self::Reachable { range }
| Self::Input { range }
| Self::Filter { range, .. }
| Self::Closure { range }
| Self::Slice { range }
| Self::ForwardSlice { range }
| Self::Defs { range, .. }
| Self::Users { range, .. }
| Self::Parent { range }
| Self::Children { range }
| Self::Root { range, .. }
| Self::Subtree { range }
| Self::Unique { range }
| Self::Sort { range }
| Self::SortBy { range, .. }
| Self::RetainSelector { range }
| Self::Reverse { range }
| Self::Head { range, .. }
| Self::Tail { range, .. }
| Self::Min { range }
| Self::MinBy { range, .. }
| Self::MinAll { range }
| Self::MinAllBy { range, .. }
| Self::Max { range }
| Self::MaxBy { range, .. }
| Self::MaxAll { range }
| Self::MaxAllBy { range, .. }
| Self::Attr { range, .. }
| Self::Names { range }
| Self::ResultTypes { range }
| Self::OperandTypes { range }
| Self::Group { range, .. }
| Self::Fixpoint { range, .. }
| Self::WhereExists { range, .. }
| Self::SetAttr { range, .. }
| Self::RemoveAttr { range, .. }
| Self::Check { range, .. }
| Self::Count { range }
| Self::Emit { range }
| Self::Format { range, .. }
| Self::Json { range } => *range,
}
}
fn is_selection_only(&self) -> bool {
match self {
Self::Paths { .. }
| Self::Match { .. }
| Self::Native { .. }
| Self::Operand { .. }
| Self::SetAttr { .. }
| Self::RemoveAttr { .. }
| Self::Count { .. }
| Self::Tally { .. }
| Self::Value { .. }
| Self::RetainSelector { .. }
| Self::NativeMap { .. }
| Self::MapBy { .. }
| Self::Literal { .. }
| Self::Comprehension { .. }
| Self::ConstructedSuffix { .. }
| Self::Projection { .. } => false,
Self::Group { expression, .. } => expression.is_selection_only(),
Self::Fixpoint { expression, .. } => expression.is_selection_only(),
_ => true,
}
}
fn first_non_selection_stage(&self) -> Option<&Stage> {
match self {
Self::Group { expression, .. }
| Self::Fixpoint {
expression: FixedPointBody::Selection(expression),
..
} => expression.first_non_selection_stage(),
_ if !self.is_selection_only() => Some(self),
_ => None,
}
}
pub(crate) fn is_terminal(&self) -> bool {
match self {
Self::Count { .. } => true,
Self::Group { expression, .. } => expression.is_terminal(),
_ => false,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum StructuredStage {
One,
MapBy {
key: Box<Expression>,
value: Box<Expression>,
},
WhereExists(Box<Expression>),
StringAttr(String),
Attributes(String),
Types {
operands: bool,
},
Spellings,
}