#![expect(
clippy::disallowed_types,
reason = "ODIN-to-JSON conversion targets the JSON data model by specification (LANG odin \
spec) (#1694)"
)]
mod parser;
use crate::v1_1::beom::core::assertion::Assertion;
use crate::v1_1::beom::core::assignment::Assignment;
use crate::v1_1::beom::core::expr_literal::ExprLiteral;
use crate::v1_1::beom::core::expr_value::ExprValue;
use crate::v1_1::beom::core::expr_value_ref::ExprValueRef;
use crate::v1_1::beom::core::expr_variable_ref::ExprVariableRef;
use crate::v1_1::beom::core::expression::Expression;
use crate::v1_1::beom::core::operator_kind::OperatorKind;
use crate::v1_1::beom::core::statement::Statement;
use crate::v1_1::beom::core::variable_declaration::VariableDeclaration;
use crate::v1_1::beom::types::expr_type_def::ExprTypeDef;
use crate::v1_1::beom::types::type_def_object_ref::TypeDefObjectRef;
use openehr_base::containers::present;
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum BelError {
#[error("BEL lex error at byte {at}: unrecognised input {text:?}")]
Lex {
at: usize,
text: String,
},
#[error("BEL parse error at byte {at}: {message}")]
Parse {
at: usize,
message: String,
},
#[error("BEL builder rejected a production at byte {at}: {message}")]
Unsupported {
at: usize,
message: String,
},
#[error("BEL parse error at byte {at}: expression nesting exceeds the limit of {limit} levels")]
NestingTooDeep {
at: usize,
limit: usize,
},
}
#[derive(Debug, Clone, PartialEq)]
pub enum BelLiteral {
Boolean(bool),
Integer(i64),
Real(f64),
String(String),
Character(char),
Date(String),
Time(String),
DateTime(String),
Duration(String),
TermCode(String),
Interval(String),
}
impl BelLiteral {
#[must_use]
pub fn to_json(&self) -> serde_json::Value {
match self {
BelLiteral::Boolean(b) => serde_json::Value::Bool(*b),
BelLiteral::Integer(i) => serde_json::Value::Number((*i).into()),
BelLiteral::Real(r) => serde_json::Number::from_f64(*r)
.map_or(serde_json::Value::Null, serde_json::Value::Number),
BelLiteral::String(s)
| BelLiteral::Date(s)
| BelLiteral::Time(s)
| BelLiteral::DateTime(s)
| BelLiteral::Duration(s)
| BelLiteral::TermCode(s)
| BelLiteral::Interval(s) => serde_json::Value::String(s.clone()),
BelLiteral::Character(c) => serde_json::Value::String(c.to_string()),
}
}
}
pub trait BelBuilder {
type Expr;
type Stmt;
fn literal(&mut self, lit: BelLiteral) -> Self::Expr;
fn variable_ref(&mut self, name: &str) -> Self::Expr;
fn path_ref(&mut self, path: &str) -> Result<Self::Expr, BelError>;
fn function_call(&mut self, name: &str, args: Vec<Self::Expr>) -> Self::Expr;
fn binary(
&mut self,
op: OperatorKind,
symbol: &str,
left: Self::Expr,
right: Self::Expr,
) -> Self::Expr;
fn unary(&mut self, op: OperatorKind, symbol: &str, operand: Self::Expr) -> Self::Expr;
fn constraint(&mut self, raw: &str, at: usize) -> Result<Self::Expr, BelError>;
fn for_all(
&mut self,
variable: &str,
collection: Self::Expr,
condition: Self::Expr,
) -> Result<Self::Expr, BelError>;
fn assertion(&mut self, tag: Option<String>, expr: Self::Expr) -> Self::Stmt;
fn assignment(&mut self, target: &str, source: Self::Expr) -> Self::Stmt;
fn variable_declaration(
&mut self,
name: &str,
type_id: &str,
init: Option<Self::Expr>,
) -> Self::Stmt;
fn constant_declaration(
&mut self,
name: &str,
type_id: &str,
value: Option<Self::Expr>,
) -> Self::Stmt;
}
pub fn parse_statements(src: &str) -> Result<Vec<Statement>, BelError> {
parse_statements_with(src, &mut BeomBuilder::default())
}
pub fn parse_statements_with<B: BelBuilder>(
src: &str,
builder: &mut B,
) -> Result<Vec<B::Stmt>, BelError> {
let tokens =
crate::v1_1::lexer::lex(crate::v1_1::lexer::Language::Bel, src).map_err(|failure| {
BelError::Lex {
at: failure.span.start,
text: failure.text,
}
})?;
parser::Parser::new(src, &tokens, builder).parse_statement_block()
}
#[derive(Debug, Default)]
pub struct BeomBuilder {
vars: std::collections::BTreeMap<String, ExprTypeDef>,
}
impl BeomBuilder {
fn type_of(&self, name: &str) -> ExprTypeDef {
self.vars
.get(name)
.cloned()
.unwrap_or_else(|| object_ref_type(name))
}
}
fn object_ref_type(type_id: &str) -> ExprTypeDef {
ExprTypeDef::TypeDefObjectRef(TypeDefObjectRef {
type_name: type_id.to_owned(),
type_anchor: serde_json::Value::Null,
})
}
impl BelBuilder for BeomBuilder {
type Expr = Expression;
type Stmt = Statement;
fn literal(&mut self, lit: BelLiteral) -> Expression {
Expression::ExprLiteral(ExprLiteral {
item: lit.to_json(),
})
}
fn variable_ref(&mut self, name: &str) -> Expression {
Expression::ExprVariableRef(ExprVariableRef {
item: VariableDeclaration {
name: name.to_owned(),
r#type: self.type_of(name),
},
})
}
fn path_ref(&mut self, path: &str) -> Result<Expression, BelError> {
Ok(Expression::ExprValueRef(ExprValueRef {
item: Some(serde_json::Value::String(path.to_owned())),
}))
}
fn function_call(&mut self, name: &str, args: Vec<Expression>) -> Expression {
Expression::ExprFunctionCall(
crate::v1_1::beom::core::expr_function_call::ExprFunctionCall {
item: Some(serde_json::Value::String(name.to_owned())),
arguments: present(args),
},
)
}
fn binary(
&mut self,
op: OperatorKind,
symbol: &str,
left: Expression,
right: Expression,
) -> Expression {
Expression::ExprBinaryOperator(Box::new(
crate::v1_1::beom::core::expr_binary_operator::ExprBinaryOperator {
precedence_overridden: None,
operator: op,
symbol: Some(symbol.to_owned()),
left_operand: Box::new(left),
right_operand: Box::new(right),
},
))
}
fn unary(&mut self, op: OperatorKind, symbol: &str, operand: Expression) -> Expression {
Expression::ExprUnaryOperator(Box::new(
crate::v1_1::beom::core::expr_unary_operator::ExprUnaryOperator {
precedence_overridden: None,
operator: op,
symbol: Some(symbol.to_owned()),
operand: Box::new(operand),
},
))
}
fn constraint(&mut self, _raw: &str, at: usize) -> Result<Expression, BelError> {
Err(BelError::Unsupported {
at,
message: "the `matches { … }` constraint leaf is an AOM extension \
(EXPR_CONSTRAINT); parse rules with the AOM builder in openehr-adl"
.to_owned(),
})
}
fn for_all(
&mut self,
_variable: &str,
collection: Expression,
condition: Expression,
) -> Result<Expression, BelError> {
let operand = match collection {
Expression::ExprValueRef(r) => r,
_ => ExprValueRef { item: None },
};
Ok(Expression::ExprForAll(Box::new(
crate::v1_1::beom::core::expr_for_all::ExprForAll {
precedence_overridden: None,
operator: OperatorKind::ForAll,
symbol: Some("for_all".to_owned()),
condition: Box::new(Assertion {
tag: None,
string_expression: None,
expression: Box::new(condition),
}),
operand,
},
)))
}
fn assertion(&mut self, tag: Option<String>, expr: Expression) -> Statement {
Statement::Assertion(Assertion {
tag,
string_expression: None,
expression: Box::new(expr),
})
}
fn assignment(&mut self, target: &str, source: Expression) -> Statement {
Statement::Assignment(Assignment {
target: VariableDeclaration {
name: target.to_owned(),
r#type: self.type_of(target),
},
source: to_expr_value(source),
})
}
fn variable_declaration(
&mut self,
name: &str,
type_id: &str,
init: Option<Expression>,
) -> Statement {
let ty = object_ref_type(type_id);
self.vars.insert(name.to_owned(), ty.clone());
drop(init);
Statement::VariableDeclaration(VariableDeclaration {
name: name.to_owned(),
r#type: ty,
})
}
fn constant_declaration(
&mut self,
name: &str,
type_id: &str,
value: Option<Expression>,
) -> Statement {
let ty = object_ref_type(type_id);
self.vars.insert(name.to_owned(), ty.clone());
drop(value);
Statement::VariableDeclaration(VariableDeclaration {
name: name.to_owned(),
r#type: ty,
})
}
}
fn to_expr_value(e: Expression) -> ExprValue {
match e {
Expression::ExprBinaryOperator(b) => ExprValue::ExprBinaryOperator(*b),
Expression::ExprForAll(f) => ExprValue::ExprForAll(*f),
Expression::ExprFunctionCall(f) => ExprValue::ExprFunctionCall(f),
Expression::ExprLiteral(l) => ExprValue::ExprLiteral(l),
Expression::ExprUnaryOperator(u) => ExprValue::ExprUnaryOperator(*u),
Expression::ExprValueRef(r) => ExprValue::ExprValueRef(r),
Expression::ExprVariableRef(v) => ExprValue::ExprVariableRef(v),
}
}