Add logical and comparison expressions
This commit is contained in:
parent
3f7dd7c692
commit
2fe54bda62
|
|
@ -124,6 +124,7 @@ pub enum Literal {
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum UnaryOp {
|
pub enum UnaryOp {
|
||||||
Neg,
|
Neg,
|
||||||
|
Not,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
|
@ -132,6 +133,14 @@ pub enum BinaryOp {
|
||||||
Sub,
|
Sub,
|
||||||
Mul,
|
Mul,
|
||||||
Div,
|
Div,
|
||||||
|
Equal,
|
||||||
|
NotEqual,
|
||||||
|
Greater,
|
||||||
|
GreaterEqual,
|
||||||
|
Less,
|
||||||
|
LessEqual,
|
||||||
|
LogicalAnd,
|
||||||
|
LogicalOr,
|
||||||
And,
|
And,
|
||||||
Patch,
|
Patch,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -347,30 +347,95 @@ impl<L: SourceLoader> Engine<L> {
|
||||||
)?;
|
)?;
|
||||||
match op {
|
match op {
|
||||||
UnaryOp::Neg => negate_number(value, span),
|
UnaryOp::Neg => negate_number(value, span),
|
||||||
|
UnaryOp::Not => negate_bool(value, span),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Expr::Binary { op, lhs, rhs } => {
|
Expr::Binary { op, lhs, rhs } => {
|
||||||
let lhs = self.eval_expr(
|
let lhs_value = self.eval_expr(
|
||||||
ExprRef {
|
ExprRef {
|
||||||
module: reference.module,
|
module: reference.module,
|
||||||
expr: lhs,
|
expr: lhs,
|
||||||
},
|
},
|
||||||
env,
|
env,
|
||||||
)?;
|
)?;
|
||||||
let rhs = self.eval_expr(
|
|
||||||
ExprRef {
|
|
||||||
module: reference.module,
|
|
||||||
expr: rhs,
|
|
||||||
},
|
|
||||||
env,
|
|
||||||
)?;
|
|
||||||
match op {
|
match op {
|
||||||
BinaryOp::Add => arithmetic(lhs, rhs, ArithmeticOp::Add, span),
|
BinaryOp::LogicalAnd => {
|
||||||
BinaryOp::Sub => arithmetic(lhs, rhs, ArithmeticOp::Sub, span),
|
if !bool_from_runtime(lhs_value, span)? {
|
||||||
BinaryOp::Mul => arithmetic(lhs, rhs, ArithmeticOp::Mul, span),
|
return Ok(RuntimeValue::Concrete(ConcreteValue::Bool(false)));
|
||||||
BinaryOp::Div => arithmetic(lhs, rhs, ArithmeticOp::Div, span),
|
}
|
||||||
BinaryOp::And => self.compose_and(lhs, rhs, span),
|
let rhs_value = self.eval_expr(
|
||||||
BinaryOp::Patch => self.patch(lhs, rhs),
|
ExprRef {
|
||||||
|
module: reference.module,
|
||||||
|
expr: rhs,
|
||||||
|
},
|
||||||
|
env,
|
||||||
|
)?;
|
||||||
|
Ok(RuntimeValue::Concrete(ConcreteValue::Bool(
|
||||||
|
bool_from_runtime(rhs_value, span)?,
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
BinaryOp::LogicalOr => {
|
||||||
|
if bool_from_runtime(lhs_value, span)? {
|
||||||
|
return Ok(RuntimeValue::Concrete(ConcreteValue::Bool(true)));
|
||||||
|
}
|
||||||
|
let rhs_value = self.eval_expr(
|
||||||
|
ExprRef {
|
||||||
|
module: reference.module,
|
||||||
|
expr: rhs,
|
||||||
|
},
|
||||||
|
env,
|
||||||
|
)?;
|
||||||
|
Ok(RuntimeValue::Concrete(ConcreteValue::Bool(
|
||||||
|
bool_from_runtime(rhs_value, span)?,
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
let rhs_value = self.eval_expr(
|
||||||
|
ExprRef {
|
||||||
|
module: reference.module,
|
||||||
|
expr: rhs,
|
||||||
|
},
|
||||||
|
env,
|
||||||
|
)?;
|
||||||
|
match op {
|
||||||
|
BinaryOp::Add => {
|
||||||
|
arithmetic(lhs_value, rhs_value, ArithmeticOp::Add, span)
|
||||||
|
}
|
||||||
|
BinaryOp::Sub => {
|
||||||
|
arithmetic(lhs_value, rhs_value, ArithmeticOp::Sub, span)
|
||||||
|
}
|
||||||
|
BinaryOp::Mul => {
|
||||||
|
arithmetic(lhs_value, rhs_value, ArithmeticOp::Mul, span)
|
||||||
|
}
|
||||||
|
BinaryOp::Div => {
|
||||||
|
arithmetic(lhs_value, rhs_value, ArithmeticOp::Div, span)
|
||||||
|
}
|
||||||
|
BinaryOp::Equal => {
|
||||||
|
compare_expr(lhs_value, rhs_value, CompareExprOp::Equal, span)
|
||||||
|
}
|
||||||
|
BinaryOp::NotEqual => {
|
||||||
|
compare_expr(lhs_value, rhs_value, CompareExprOp::NotEqual, span)
|
||||||
|
}
|
||||||
|
BinaryOp::Greater => {
|
||||||
|
compare_expr(lhs_value, rhs_value, CompareExprOp::Greater, span)
|
||||||
|
}
|
||||||
|
BinaryOp::GreaterEqual => compare_expr(
|
||||||
|
lhs_value,
|
||||||
|
rhs_value,
|
||||||
|
CompareExprOp::GreaterEqual,
|
||||||
|
span,
|
||||||
|
),
|
||||||
|
BinaryOp::Less => {
|
||||||
|
compare_expr(lhs_value, rhs_value, CompareExprOp::Less, span)
|
||||||
|
}
|
||||||
|
BinaryOp::LessEqual => {
|
||||||
|
compare_expr(lhs_value, rhs_value, CompareExprOp::LessEqual, span)
|
||||||
|
}
|
||||||
|
BinaryOp::And => self.compose_and(lhs_value, rhs_value, span),
|
||||||
|
BinaryOp::Patch => self.patch(lhs_value, rhs_value),
|
||||||
|
BinaryOp::LogicalAnd | BinaryOp::LogicalOr => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Expr::Default { base, fallback } => {
|
Expr::Default { base, fallback } => {
|
||||||
|
|
@ -1023,6 +1088,16 @@ enum ArithmeticOp {
|
||||||
Div,
|
Div,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
enum CompareExprOp {
|
||||||
|
Equal,
|
||||||
|
NotEqual,
|
||||||
|
Greater,
|
||||||
|
GreaterEqual,
|
||||||
|
Less,
|
||||||
|
LessEqual,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
enum Number {
|
enum Number {
|
||||||
Int(i64),
|
Int(i64),
|
||||||
|
|
@ -1046,6 +1121,93 @@ fn negate_number(value: RuntimeValue, span: Span) -> Result<RuntimeValue> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn negate_bool(value: RuntimeValue, span: Span) -> Result<RuntimeValue> {
|
||||||
|
Ok(RuntimeValue::Concrete(ConcreteValue::Bool(
|
||||||
|
!bool_from_runtime(value, span)?,
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bool_from_runtime(value: RuntimeValue, span: Span) -> Result<bool> {
|
||||||
|
match value {
|
||||||
|
RuntimeValue::Concrete(ConcreteValue::Bool(value)) => Ok(value),
|
||||||
|
_ => Err(Diagnostic::new(
|
||||||
|
DiagnosticKind::TypeMismatch,
|
||||||
|
span,
|
||||||
|
"logical operators expect boolean values",
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compare_expr(
|
||||||
|
lhs: RuntimeValue,
|
||||||
|
rhs: RuntimeValue,
|
||||||
|
op: CompareExprOp,
|
||||||
|
span: Span,
|
||||||
|
) -> Result<RuntimeValue> {
|
||||||
|
let result = match op {
|
||||||
|
CompareExprOp::Equal => scalar_equal(&lhs, &rhs, span)?,
|
||||||
|
CompareExprOp::NotEqual => !scalar_equal(&lhs, &rhs, span)?,
|
||||||
|
CompareExprOp::Greater => compare_numbers(lhs, rhs, |lhs, rhs| lhs > rhs, span)?,
|
||||||
|
CompareExprOp::GreaterEqual => compare_numbers(lhs, rhs, |lhs, rhs| lhs >= rhs, span)?,
|
||||||
|
CompareExprOp::Less => compare_numbers(lhs, rhs, |lhs, rhs| lhs < rhs, span)?,
|
||||||
|
CompareExprOp::LessEqual => compare_numbers(lhs, rhs, |lhs, rhs| lhs <= rhs, span)?,
|
||||||
|
};
|
||||||
|
Ok(RuntimeValue::Concrete(ConcreteValue::Bool(result)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn scalar_equal(lhs: &RuntimeValue, rhs: &RuntimeValue, span: Span) -> Result<bool> {
|
||||||
|
match (lhs, rhs) {
|
||||||
|
(
|
||||||
|
RuntimeValue::Concrete(ConcreteValue::String(lhs)),
|
||||||
|
RuntimeValue::Concrete(ConcreteValue::String(rhs)),
|
||||||
|
) => Ok(lhs == rhs),
|
||||||
|
(
|
||||||
|
RuntimeValue::Concrete(ConcreteValue::Bool(lhs)),
|
||||||
|
RuntimeValue::Concrete(ConcreteValue::Bool(rhs)),
|
||||||
|
) => Ok(lhs == rhs),
|
||||||
|
(
|
||||||
|
RuntimeValue::Concrete(ConcreteValue::Int(lhs)),
|
||||||
|
RuntimeValue::Concrete(ConcreteValue::Int(rhs)),
|
||||||
|
) => Ok(lhs == rhs),
|
||||||
|
(
|
||||||
|
RuntimeValue::Concrete(ConcreteValue::Float(lhs)),
|
||||||
|
RuntimeValue::Concrete(ConcreteValue::Float(rhs)),
|
||||||
|
) => Ok(lhs == rhs),
|
||||||
|
(
|
||||||
|
RuntimeValue::Concrete(ConcreteValue::Int(lhs)),
|
||||||
|
RuntimeValue::Concrete(ConcreteValue::Float(rhs)),
|
||||||
|
) => Ok(*lhs as f64 == *rhs),
|
||||||
|
(
|
||||||
|
RuntimeValue::Concrete(ConcreteValue::Float(lhs)),
|
||||||
|
RuntimeValue::Concrete(ConcreteValue::Int(rhs)),
|
||||||
|
) => Ok(*lhs == *rhs as f64),
|
||||||
|
_ => Err(Diagnostic::new(
|
||||||
|
DiagnosticKind::TypeMismatch,
|
||||||
|
span,
|
||||||
|
"equality operators expect comparable scalar values",
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compare_numbers(
|
||||||
|
lhs: RuntimeValue,
|
||||||
|
rhs: RuntimeValue,
|
||||||
|
compare: impl FnOnce(f64, f64) -> bool,
|
||||||
|
span: Span,
|
||||||
|
) -> Result<bool> {
|
||||||
|
let lhs = number_from_runtime(lhs).ok_or_else(|| comparison_type_error(span))?;
|
||||||
|
let rhs = number_from_runtime(rhs).ok_or_else(|| comparison_type_error(span))?;
|
||||||
|
Ok(compare(number_to_f64(lhs), number_to_f64(rhs)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn comparison_type_error(span: Span) -> Diagnostic {
|
||||||
|
Diagnostic::new(
|
||||||
|
DiagnosticKind::TypeMismatch,
|
||||||
|
span,
|
||||||
|
"ordering operators expect numeric values",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fn arithmetic(
|
fn arithmetic(
|
||||||
lhs: RuntimeValue,
|
lhs: RuntimeValue,
|
||||||
rhs: RuntimeValue,
|
rhs: RuntimeValue,
|
||||||
|
|
@ -1233,6 +1395,50 @@ mod tests {
|
||||||
assert!(engine.eval_root().is_err());
|
assert!(engine.eval_root().is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn evaluates_logical_and_comparison_expressions() {
|
||||||
|
let data = eval_data(
|
||||||
|
r#"
|
||||||
|
{
|
||||||
|
enabled = true && !false;
|
||||||
|
fallback = false || true;
|
||||||
|
compare = 9000 + 443 > 9442;
|
||||||
|
equality = 1 == 1.0;
|
||||||
|
inequality = "prod" != "dev";
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
let Data::Object(fields) = data else { panic!() };
|
||||||
|
assert_eq!(fields[0].value, Data::Bool(true));
|
||||||
|
assert_eq!(fields[1].value, Data::Bool(true));
|
||||||
|
assert_eq!(fields[2].value, Data::Bool(true));
|
||||||
|
assert_eq!(fields[3].value, Data::Bool(true));
|
||||||
|
assert_eq!(fields[4].value, Data::Bool(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn logical_operators_short_circuit() {
|
||||||
|
let false_and_missing = eval_data("false && missing_identifier");
|
||||||
|
assert_eq!(false_and_missing, Data::Bool(false));
|
||||||
|
|
||||||
|
let true_or_missing = eval_data("true || missing_identifier");
|
||||||
|
assert_eq!(true_or_missing, Data::Bool(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rejects_invalid_logical_operands() {
|
||||||
|
let parsed = parse_source("true && 1").unwrap();
|
||||||
|
let mut engine = Engine::from_parse(parsed.ast, parsed.root);
|
||||||
|
assert!(engine.eval_root().is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rejects_invalid_comparison_operands() {
|
||||||
|
let parsed = parse_source("\"a\" < \"b\"").unwrap();
|
||||||
|
let mut engine = Engine::from_parse(parsed.ast, parsed.root);
|
||||||
|
assert!(engine.eval_root().is_err());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn composes_schema_and_value() {
|
fn composes_schema_and_value() {
|
||||||
let data = eval_data(
|
let data = eval_data(
|
||||||
|
|
|
||||||
|
|
@ -34,8 +34,13 @@ pub enum TokenKind {
|
||||||
Dot,
|
Dot,
|
||||||
Colon,
|
Colon,
|
||||||
Equal,
|
Equal,
|
||||||
|
EqualEqual,
|
||||||
|
Bang,
|
||||||
|
BangEqual,
|
||||||
Arrow,
|
Arrow,
|
||||||
Amp,
|
Amp,
|
||||||
|
AmpAmp,
|
||||||
|
PipePipe,
|
||||||
Plus,
|
Plus,
|
||||||
Minus,
|
Minus,
|
||||||
Star,
|
Star,
|
||||||
|
|
@ -142,7 +147,22 @@ impl<'a> Lexer<'a> {
|
||||||
}
|
}
|
||||||
b'&' => {
|
b'&' => {
|
||||||
self.pos += 1;
|
self.pos += 1;
|
||||||
TokenKind::Amp
|
if self.consume(b'&') {
|
||||||
|
TokenKind::AmpAmp
|
||||||
|
} else {
|
||||||
|
TokenKind::Amp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
b'|' => {
|
||||||
|
self.pos += 1;
|
||||||
|
if self.consume(b'|') {
|
||||||
|
TokenKind::PipePipe
|
||||||
|
} else {
|
||||||
|
return Err(Diagnostic::syntax(
|
||||||
|
self.span(start, self.pos),
|
||||||
|
"expected '|' after '|'",
|
||||||
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
b'+' => {
|
b'+' => {
|
||||||
self.pos += 1;
|
self.pos += 1;
|
||||||
|
|
@ -160,10 +180,20 @@ impl<'a> Lexer<'a> {
|
||||||
self.pos += 1;
|
self.pos += 1;
|
||||||
if self.consume(b'>') {
|
if self.consume(b'>') {
|
||||||
TokenKind::Arrow
|
TokenKind::Arrow
|
||||||
|
} else if self.consume(b'=') {
|
||||||
|
TokenKind::EqualEqual
|
||||||
} else {
|
} else {
|
||||||
TokenKind::Equal
|
TokenKind::Equal
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
b'!' => {
|
||||||
|
self.pos += 1;
|
||||||
|
if self.consume(b'=') {
|
||||||
|
TokenKind::BangEqual
|
||||||
|
} else {
|
||||||
|
TokenKind::Bang
|
||||||
|
}
|
||||||
|
}
|
||||||
b'>' => {
|
b'>' => {
|
||||||
self.pos += 1;
|
self.pos += 1;
|
||||||
if self.consume(b'=') {
|
if self.consume(b'=') {
|
||||||
|
|
|
||||||
|
|
@ -147,6 +147,70 @@ impl Parser {
|
||||||
},
|
},
|
||||||
span,
|
span,
|
||||||
),
|
),
|
||||||
|
InfixKind::Equal => self.ast.push(
|
||||||
|
Expr::Binary {
|
||||||
|
op: BinaryOp::Equal,
|
||||||
|
lhs,
|
||||||
|
rhs,
|
||||||
|
},
|
||||||
|
span,
|
||||||
|
),
|
||||||
|
InfixKind::NotEqual => self.ast.push(
|
||||||
|
Expr::Binary {
|
||||||
|
op: BinaryOp::NotEqual,
|
||||||
|
lhs,
|
||||||
|
rhs,
|
||||||
|
},
|
||||||
|
span,
|
||||||
|
),
|
||||||
|
InfixKind::Greater => self.ast.push(
|
||||||
|
Expr::Binary {
|
||||||
|
op: BinaryOp::Greater,
|
||||||
|
lhs,
|
||||||
|
rhs,
|
||||||
|
},
|
||||||
|
span,
|
||||||
|
),
|
||||||
|
InfixKind::GreaterEqual => self.ast.push(
|
||||||
|
Expr::Binary {
|
||||||
|
op: BinaryOp::GreaterEqual,
|
||||||
|
lhs,
|
||||||
|
rhs,
|
||||||
|
},
|
||||||
|
span,
|
||||||
|
),
|
||||||
|
InfixKind::Less => self.ast.push(
|
||||||
|
Expr::Binary {
|
||||||
|
op: BinaryOp::Less,
|
||||||
|
lhs,
|
||||||
|
rhs,
|
||||||
|
},
|
||||||
|
span,
|
||||||
|
),
|
||||||
|
InfixKind::LessEqual => self.ast.push(
|
||||||
|
Expr::Binary {
|
||||||
|
op: BinaryOp::LessEqual,
|
||||||
|
lhs,
|
||||||
|
rhs,
|
||||||
|
},
|
||||||
|
span,
|
||||||
|
),
|
||||||
|
InfixKind::LogicalAnd => self.ast.push(
|
||||||
|
Expr::Binary {
|
||||||
|
op: BinaryOp::LogicalAnd,
|
||||||
|
lhs,
|
||||||
|
rhs,
|
||||||
|
},
|
||||||
|
span,
|
||||||
|
),
|
||||||
|
InfixKind::LogicalOr => self.ast.push(
|
||||||
|
Expr::Binary {
|
||||||
|
op: BinaryOp::LogicalOr,
|
||||||
|
lhs,
|
||||||
|
rhs,
|
||||||
|
},
|
||||||
|
span,
|
||||||
|
),
|
||||||
InfixKind::And => self.ast.push(
|
InfixKind::And => self.ast.push(
|
||||||
Expr::Binary {
|
Expr::Binary {
|
||||||
op: BinaryOp::And,
|
op: BinaryOp::And,
|
||||||
|
|
@ -206,7 +270,7 @@ impl Parser {
|
||||||
TokenKind::Match => self.parse_match(token.span),
|
TokenKind::Match => self.parse_match(token.span),
|
||||||
TokenKind::Import => self.parse_import(token.span),
|
TokenKind::Import => self.parse_import(token.span),
|
||||||
TokenKind::Minus => {
|
TokenKind::Minus => {
|
||||||
let expr = self.parse_expr(11)?;
|
let expr = self.parse_expr(17)?;
|
||||||
let span = token.span.join(self.ast.span(expr));
|
let span = token.span.join(self.ast.span(expr));
|
||||||
Ok(self.ast.push(
|
Ok(self.ast.push(
|
||||||
Expr::Unary {
|
Expr::Unary {
|
||||||
|
|
@ -216,6 +280,17 @@ impl Parser {
|
||||||
span,
|
span,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
TokenKind::Bang => {
|
||||||
|
let expr = self.parse_expr(17)?;
|
||||||
|
let span = token.span.join(self.ast.span(expr));
|
||||||
|
Ok(self.ast.push(
|
||||||
|
Expr::Unary {
|
||||||
|
op: UnaryOp::Not,
|
||||||
|
expr,
|
||||||
|
},
|
||||||
|
span,
|
||||||
|
))
|
||||||
|
}
|
||||||
TokenKind::Gt | TokenKind::Gte | TokenKind::Lt | TokenKind::Lte => {
|
TokenKind::Gt | TokenKind::Gte | TokenKind::Lt | TokenKind::Lte => {
|
||||||
let op = match token.kind {
|
let op = match token.kind {
|
||||||
TokenKind::Gt => CompareOp::Gt,
|
TokenKind::Gt => CompareOp::Gt,
|
||||||
|
|
@ -224,7 +299,7 @@ impl Parser {
|
||||||
TokenKind::Lte => CompareOp::Lte,
|
TokenKind::Lte => CompareOp::Lte,
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
let value = self.parse_expr(6)?;
|
let value = self.parse_expr(12)?;
|
||||||
let span = token.span.join(self.ast.span(value));
|
let span = token.span.join(self.ast.span(value));
|
||||||
Ok(self.ast.push(Expr::CompareConstraint { op, value }, span))
|
Ok(self.ast.push(Expr::CompareConstraint { op, value }, span))
|
||||||
}
|
}
|
||||||
|
|
@ -449,10 +524,18 @@ impl Parser {
|
||||||
TokenKind::Default => Some((InfixKind::Default, 1, 2)),
|
TokenKind::Default => Some((InfixKind::Default, 1, 2)),
|
||||||
TokenKind::SlashSlash => Some((InfixKind::Patch, 3, 4)),
|
TokenKind::SlashSlash => Some((InfixKind::Patch, 3, 4)),
|
||||||
TokenKind::Amp => Some((InfixKind::And, 5, 6)),
|
TokenKind::Amp => Some((InfixKind::And, 5, 6)),
|
||||||
TokenKind::Plus => Some((InfixKind::Add, 7, 8)),
|
TokenKind::PipePipe => Some((InfixKind::LogicalOr, 7, 8)),
|
||||||
TokenKind::Minus => Some((InfixKind::Sub, 7, 8)),
|
TokenKind::AmpAmp => Some((InfixKind::LogicalAnd, 9, 10)),
|
||||||
TokenKind::Star => Some((InfixKind::Mul, 9, 10)),
|
TokenKind::EqualEqual => Some((InfixKind::Equal, 11, 12)),
|
||||||
TokenKind::Slash => Some((InfixKind::Div, 9, 10)),
|
TokenKind::BangEqual => Some((InfixKind::NotEqual, 11, 12)),
|
||||||
|
TokenKind::Gt => Some((InfixKind::Greater, 11, 12)),
|
||||||
|
TokenKind::Gte => Some((InfixKind::GreaterEqual, 11, 12)),
|
||||||
|
TokenKind::Lt => Some((InfixKind::Less, 11, 12)),
|
||||||
|
TokenKind::Lte => Some((InfixKind::LessEqual, 11, 12)),
|
||||||
|
TokenKind::Plus => Some((InfixKind::Add, 13, 14)),
|
||||||
|
TokenKind::Minus => Some((InfixKind::Sub, 13, 14)),
|
||||||
|
TokenKind::Star => Some((InfixKind::Mul, 15, 16)),
|
||||||
|
TokenKind::Slash => Some((InfixKind::Div, 15, 16)),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -549,6 +632,14 @@ enum InfixKind {
|
||||||
Sub,
|
Sub,
|
||||||
Mul,
|
Mul,
|
||||||
Div,
|
Div,
|
||||||
|
Equal,
|
||||||
|
NotEqual,
|
||||||
|
Greater,
|
||||||
|
GreaterEqual,
|
||||||
|
Less,
|
||||||
|
LessEqual,
|
||||||
|
LogicalAnd,
|
||||||
|
LogicalOr,
|
||||||
And,
|
And,
|
||||||
Patch,
|
Patch,
|
||||||
Default,
|
Default,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
# Logical and Comparison Expressions
|
||||||
|
|
||||||
|
Decodal supports boolean logic over concrete `Bool` values and comparison over concrete scalar values.
|
||||||
|
|
||||||
|
```dcdl
|
||||||
|
{
|
||||||
|
is_prod = env == "prod";
|
||||||
|
high_port = port > 9000;
|
||||||
|
enabled = is_prod && high_port;
|
||||||
|
disabled = !enabled;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Logical operators
|
||||||
|
|
||||||
|
- `!expr` negates a concrete `Bool`.
|
||||||
|
- `lhs && rhs` returns boolean AND.
|
||||||
|
- `lhs || rhs` returns boolean OR.
|
||||||
|
|
||||||
|
`&&` and `||` short-circuit: the right-hand side is evaluated only when needed.
|
||||||
|
Logical operands must evaluate to concrete `Bool` values.
|
||||||
|
|
||||||
|
## Comparison operators
|
||||||
|
|
||||||
|
- `==`
|
||||||
|
- `!=`
|
||||||
|
- `<`
|
||||||
|
- `<=`
|
||||||
|
- `>`
|
||||||
|
- `>=`
|
||||||
|
|
||||||
|
`==` and `!=` compare concrete scalar values: `String`, `Bool`, `Int`, and `Float`.
|
||||||
|
`Int` and `Float` can be compared to each other numerically.
|
||||||
|
|
||||||
|
Ordering operators `<`, `<=`, `>`, and `>=` compare concrete numeric values only.
|
||||||
|
They are separate from prefix comparison constraints such as `> 443`.
|
||||||
|
|
||||||
|
```dcdl
|
||||||
|
port = Int & > 443 default 9443;
|
||||||
|
is_high = port > 9000;
|
||||||
|
```
|
||||||
|
|
@ -7,12 +7,15 @@
|
||||||
優先順位は高い順に以下である。
|
優先順位は高い順に以下である。
|
||||||
|
|
||||||
1. 関数呼び出しとフィールド参照
|
1. 関数呼び出しとフィールド参照
|
||||||
2. unary `-`
|
2. unary `!` `-`
|
||||||
3. `*` `/`
|
3. `*` `/`
|
||||||
4. `+` `-`
|
4. `+` `-`
|
||||||
5. `&`
|
5. `==` `!=` `<` `<=` `>` `>=`
|
||||||
6. `//`
|
6. `&&`
|
||||||
7. `default`
|
7. `||`
|
||||||
|
8. `&`
|
||||||
|
9. `//`
|
||||||
|
10. `default`
|
||||||
|
|
||||||
同じ優先順位の二項演算子は左結合である。
|
同じ優先順位の二項演算子は左結合である。
|
||||||
`default` は右結合である。
|
`default` は右結合である。
|
||||||
|
|
@ -22,6 +25,15 @@
|
||||||
`+` `-` `*` `/` は具体的な `Int` / `Float` に対する四則演算である。
|
`+` `-` `*` `/` は具体的な `Int` / `Float` に対する四則演算である。
|
||||||
詳しくは [Arithmetic Expression](./expression/arithmetic.md) を参照する。
|
詳しくは [Arithmetic Expression](./expression/arithmetic.md) を参照する。
|
||||||
|
|
||||||
|
## Logical and comparison operators
|
||||||
|
|
||||||
|
`!` `&&` `||` は concrete `Bool` に対する論理演算である。
|
||||||
|
`&&` と `||` は短絡評価される。
|
||||||
|
|
||||||
|
`==` `!=` は concrete scalar value を比較する。
|
||||||
|
`<` `<=` `>` `>=` は concrete numeric value を比較する。
|
||||||
|
詳しくは [Logical and Comparison Expressions](./expression/logical-and-comparison.md) を参照する。
|
||||||
|
|
||||||
## `&`: 制約合成
|
## `&`: 制約合成
|
||||||
|
|
||||||
`&` は値・制約・構造を合成する演算子である。
|
`&` は値・制約・構造を合成する演算子である。
|
||||||
|
|
|
||||||
|
|
@ -115,12 +115,14 @@ rec
|
||||||
主要な演算子は以下である。
|
主要な演算子は以下である。
|
||||||
|
|
||||||
```text
|
```text
|
||||||
+ - * / 四則演算
|
+ - * / 四則演算
|
||||||
& 制約合成
|
! && || 論理演算
|
||||||
// patch 合成
|
== != < <= > >= 比較式
|
||||||
default fallback 指定
|
& 制約合成
|
||||||
=> 関数
|
// patch 合成
|
||||||
. フィールド参照 / ドットパス定義
|
default fallback 指定
|
||||||
|
=> 関数
|
||||||
|
. フィールド参照 / ドットパス定義
|
||||||
```
|
```
|
||||||
|
|
||||||
演算子の優先順位は [合成演算子](./operators.md) で定義する。
|
演算子の優先順位は [合成演算子](./operators.md) で定義する。
|
||||||
|
|
|
||||||
|
|
@ -102,3 +102,30 @@ Arithmetic
|
||||||
right: (literal (integer))))
|
right: (literal (integer))))
|
||||||
right: (unary_expression
|
right: (unary_expression
|
||||||
operand: (literal (integer)))))))
|
operand: (literal (integer)))))))
|
||||||
|
|
||||||
|
==================
|
||||||
|
Logical and comparison
|
||||||
|
==================
|
||||||
|
{
|
||||||
|
enabled = env == "prod" && replicas > 1;
|
||||||
|
disabled = !enabled || false;
|
||||||
|
}
|
||||||
|
---
|
||||||
|
|
||||||
|
(source_file
|
||||||
|
(object
|
||||||
|
(field_definition
|
||||||
|
path: (field_path (identifier))
|
||||||
|
value: (binary_expression
|
||||||
|
left: (binary_expression
|
||||||
|
left: (identifier)
|
||||||
|
right: (literal (string)))
|
||||||
|
right: (binary_expression
|
||||||
|
left: (identifier)
|
||||||
|
right: (literal (integer)))))
|
||||||
|
(field_definition
|
||||||
|
path: (field_path (identifier))
|
||||||
|
value: (binary_expression
|
||||||
|
left: (unary_expression
|
||||||
|
operand: (identifier))
|
||||||
|
right: (literal (boolean))))))
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,15 @@
|
||||||
const PREC = {
|
const PREC = {
|
||||||
DEFAULT: 1,
|
DEFAULT: 1,
|
||||||
PATCH: 2,
|
PATCH: 2,
|
||||||
AND: 3,
|
COMPOSE: 3,
|
||||||
ADD: 4,
|
OR: 4,
|
||||||
MUL: 5,
|
LOGICAL_AND: 5,
|
||||||
UNARY: 6,
|
COMPARE: 6,
|
||||||
CALL: 7,
|
ADD: 7,
|
||||||
PATH: 8,
|
MUL: 8,
|
||||||
|
UNARY: 9,
|
||||||
|
CALL: 10,
|
||||||
|
PATH: 11,
|
||||||
};
|
};
|
||||||
|
|
||||||
function commaSep(rule) {
|
function commaSep(rule) {
|
||||||
|
|
@ -163,17 +166,32 @@ module.exports = grammar({
|
||||||
field('field', $.identifier),
|
field('field', $.identifier),
|
||||||
)),
|
)),
|
||||||
|
|
||||||
comparison_constraint: $ => prec(6, seq(
|
comparison_constraint: $ => prec.right(PREC.UNARY + 1, seq(
|
||||||
field('operator', choice('>', '>=', '<', '<=')),
|
field('operator', choice('>', '>=', '<', '<=')),
|
||||||
field('value', $._expression),
|
field('value', $._expression),
|
||||||
)),
|
)),
|
||||||
|
|
||||||
unary_expression: $ => prec(PREC.UNARY, seq(
|
unary_expression: $ => prec(PREC.UNARY, seq(
|
||||||
field('operator', '-'),
|
field('operator', choice('-', '!')),
|
||||||
field('operand', $._expression),
|
field('operand', $._expression),
|
||||||
)),
|
)),
|
||||||
|
|
||||||
binary_expression: $ => choice(
|
binary_expression: $ => choice(
|
||||||
|
prec.left(PREC.OR, seq(
|
||||||
|
field('left', $._expression),
|
||||||
|
field('operator', token(prec(2, '||'))),
|
||||||
|
field('right', $._expression),
|
||||||
|
)),
|
||||||
|
prec.left(PREC.LOGICAL_AND, seq(
|
||||||
|
field('left', $._expression),
|
||||||
|
field('operator', token(prec(2, '&&'))),
|
||||||
|
field('right', $._expression),
|
||||||
|
)),
|
||||||
|
prec.left(PREC.COMPARE, seq(
|
||||||
|
field('left', $._expression),
|
||||||
|
field('operator', choice('==', '!=', '>', '>=', '<', '<=')),
|
||||||
|
field('right', $._expression),
|
||||||
|
)),
|
||||||
prec.left(PREC.ADD, seq(
|
prec.left(PREC.ADD, seq(
|
||||||
field('left', $._expression),
|
field('left', $._expression),
|
||||||
field('operator', choice('+', '-')),
|
field('operator', choice('+', '-')),
|
||||||
|
|
@ -184,7 +202,7 @@ module.exports = grammar({
|
||||||
field('operator', choice('*', '/')),
|
field('operator', choice('*', '/')),
|
||||||
field('right', $._expression),
|
field('right', $._expression),
|
||||||
)),
|
)),
|
||||||
prec.left(PREC.AND, seq(
|
prec.left(PREC.COMPOSE, seq(
|
||||||
field('left', $._expression),
|
field('left', $._expression),
|
||||||
field('operator', '&'),
|
field('operator', '&'),
|
||||||
field('right', $._expression),
|
field('right', $._expression),
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,11 @@
|
||||||
"-"
|
"-"
|
||||||
"*"
|
"*"
|
||||||
"/"
|
"/"
|
||||||
|
"&&"
|
||||||
|
"||"
|
||||||
|
"!"
|
||||||
|
"=="
|
||||||
|
"!="
|
||||||
"=>"
|
"=>"
|
||||||
"="
|
"="
|
||||||
">"
|
">"
|
||||||
|
|
|
||||||
163
editors/tree-sitter-decodal/src/grammar.json
generated
163
editors/tree-sitter-decodal/src/grammar.json
generated
|
|
@ -716,7 +716,7 @@
|
||||||
},
|
},
|
||||||
"call_expression": {
|
"call_expression": {
|
||||||
"type": "PREC_LEFT",
|
"type": "PREC_LEFT",
|
||||||
"value": 7,
|
"value": 10,
|
||||||
"content": {
|
"content": {
|
||||||
"type": "SEQ",
|
"type": "SEQ",
|
||||||
"members": [
|
"members": [
|
||||||
|
|
@ -786,7 +786,7 @@
|
||||||
},
|
},
|
||||||
"path_expression": {
|
"path_expression": {
|
||||||
"type": "PREC_LEFT",
|
"type": "PREC_LEFT",
|
||||||
"value": 8,
|
"value": 11,
|
||||||
"content": {
|
"content": {
|
||||||
"type": "SEQ",
|
"type": "SEQ",
|
||||||
"members": [
|
"members": [
|
||||||
|
|
@ -814,8 +814,8 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"comparison_constraint": {
|
"comparison_constraint": {
|
||||||
"type": "PREC",
|
"type": "PREC_RIGHT",
|
||||||
"value": 6,
|
"value": 10,
|
||||||
"content": {
|
"content": {
|
||||||
"type": "SEQ",
|
"type": "SEQ",
|
||||||
"members": [
|
"members": [
|
||||||
|
|
@ -857,7 +857,7 @@
|
||||||
},
|
},
|
||||||
"unary_expression": {
|
"unary_expression": {
|
||||||
"type": "PREC",
|
"type": "PREC",
|
||||||
"value": 6,
|
"value": 9,
|
||||||
"content": {
|
"content": {
|
||||||
"type": "SEQ",
|
"type": "SEQ",
|
||||||
"members": [
|
"members": [
|
||||||
|
|
@ -865,8 +865,17 @@
|
||||||
"type": "FIELD",
|
"type": "FIELD",
|
||||||
"name": "operator",
|
"name": "operator",
|
||||||
"content": {
|
"content": {
|
||||||
"type": "STRING",
|
"type": "CHOICE",
|
||||||
"value": "-"
|
"members": [
|
||||||
|
{
|
||||||
|
"type": "STRING",
|
||||||
|
"value": "-"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "STRING",
|
||||||
|
"value": "!"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -886,6 +895,144 @@
|
||||||
{
|
{
|
||||||
"type": "PREC_LEFT",
|
"type": "PREC_LEFT",
|
||||||
"value": 4,
|
"value": 4,
|
||||||
|
"content": {
|
||||||
|
"type": "SEQ",
|
||||||
|
"members": [
|
||||||
|
{
|
||||||
|
"type": "FIELD",
|
||||||
|
"name": "left",
|
||||||
|
"content": {
|
||||||
|
"type": "SYMBOL",
|
||||||
|
"name": "_expression"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "FIELD",
|
||||||
|
"name": "operator",
|
||||||
|
"content": {
|
||||||
|
"type": "TOKEN",
|
||||||
|
"content": {
|
||||||
|
"type": "PREC",
|
||||||
|
"value": 2,
|
||||||
|
"content": {
|
||||||
|
"type": "STRING",
|
||||||
|
"value": "||"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "FIELD",
|
||||||
|
"name": "right",
|
||||||
|
"content": {
|
||||||
|
"type": "SYMBOL",
|
||||||
|
"name": "_expression"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "PREC_LEFT",
|
||||||
|
"value": 5,
|
||||||
|
"content": {
|
||||||
|
"type": "SEQ",
|
||||||
|
"members": [
|
||||||
|
{
|
||||||
|
"type": "FIELD",
|
||||||
|
"name": "left",
|
||||||
|
"content": {
|
||||||
|
"type": "SYMBOL",
|
||||||
|
"name": "_expression"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "FIELD",
|
||||||
|
"name": "operator",
|
||||||
|
"content": {
|
||||||
|
"type": "TOKEN",
|
||||||
|
"content": {
|
||||||
|
"type": "PREC",
|
||||||
|
"value": 2,
|
||||||
|
"content": {
|
||||||
|
"type": "STRING",
|
||||||
|
"value": "&&"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "FIELD",
|
||||||
|
"name": "right",
|
||||||
|
"content": {
|
||||||
|
"type": "SYMBOL",
|
||||||
|
"name": "_expression"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "PREC_LEFT",
|
||||||
|
"value": 6,
|
||||||
|
"content": {
|
||||||
|
"type": "SEQ",
|
||||||
|
"members": [
|
||||||
|
{
|
||||||
|
"type": "FIELD",
|
||||||
|
"name": "left",
|
||||||
|
"content": {
|
||||||
|
"type": "SYMBOL",
|
||||||
|
"name": "_expression"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "FIELD",
|
||||||
|
"name": "operator",
|
||||||
|
"content": {
|
||||||
|
"type": "CHOICE",
|
||||||
|
"members": [
|
||||||
|
{
|
||||||
|
"type": "STRING",
|
||||||
|
"value": "=="
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "STRING",
|
||||||
|
"value": "!="
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "STRING",
|
||||||
|
"value": ">"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "STRING",
|
||||||
|
"value": ">="
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "STRING",
|
||||||
|
"value": "<"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "STRING",
|
||||||
|
"value": "<="
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "FIELD",
|
||||||
|
"name": "right",
|
||||||
|
"content": {
|
||||||
|
"type": "SYMBOL",
|
||||||
|
"name": "_expression"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "PREC_LEFT",
|
||||||
|
"value": 7,
|
||||||
"content": {
|
"content": {
|
||||||
"type": "SEQ",
|
"type": "SEQ",
|
||||||
"members": [
|
"members": [
|
||||||
|
|
@ -927,7 +1074,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "PREC_LEFT",
|
"type": "PREC_LEFT",
|
||||||
"value": 5,
|
"value": 8,
|
||||||
"content": {
|
"content": {
|
||||||
"type": "SEQ",
|
"type": "SEQ",
|
||||||
"members": [
|
"members": [
|
||||||
|
|
|
||||||
56
editors/tree-sitter-decodal/src/node-types.json
generated
56
editors/tree-sitter-decodal/src/node-types.json
generated
|
|
@ -152,10 +152,18 @@
|
||||||
"multiple": false,
|
"multiple": false,
|
||||||
"required": true,
|
"required": true,
|
||||||
"types": [
|
"types": [
|
||||||
|
{
|
||||||
|
"type": "!=",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "&",
|
"type": "&",
|
||||||
"named": false
|
"named": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "&&",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "*",
|
"type": "*",
|
||||||
"named": false
|
"named": false
|
||||||
|
|
@ -175,6 +183,30 @@
|
||||||
{
|
{
|
||||||
"type": "//",
|
"type": "//",
|
||||||
"named": false
|
"named": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "<",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "<=",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "==",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": ">",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": ">=",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "||",
|
||||||
|
"named": false
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
@ -1616,6 +1648,10 @@
|
||||||
"multiple": false,
|
"multiple": false,
|
||||||
"required": true,
|
"required": true,
|
||||||
"types": [
|
"types": [
|
||||||
|
{
|
||||||
|
"type": "!",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "-",
|
"type": "-",
|
||||||
"named": false
|
"named": false
|
||||||
|
|
@ -1624,10 +1660,22 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "!",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "!=",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "&",
|
"type": "&",
|
||||||
"named": false
|
"named": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "&&",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "(",
|
"type": "(",
|
||||||
"named": false
|
"named": false
|
||||||
|
|
@ -1684,6 +1732,10 @@
|
||||||
"type": "=",
|
"type": "=",
|
||||||
"named": false
|
"named": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "==",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "=>",
|
"type": "=>",
|
||||||
"named": false
|
"named": false
|
||||||
|
|
@ -1764,6 +1816,10 @@
|
||||||
"type": "{",
|
"type": "{",
|
||||||
"named": false
|
"named": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "||",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "}",
|
"type": "}",
|
||||||
"named": false
|
"named": false
|
||||||
|
|
|
||||||
10492
editors/tree-sitter-decodal/src/parser.c
generated
10492
editors/tree-sitter-decodal/src/parser.c
generated
File diff suppressed because it is too large
Load Diff
11
examples/logical.dcdl
Normal file
11
examples/logical.dcdl
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
let
|
||||||
|
env = "prod";
|
||||||
|
replicas = 3;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
is_prod = env == "prod";
|
||||||
|
scaled = replicas > 1;
|
||||||
|
enabled = env == "prod" && replicas > 1;
|
||||||
|
disabled = !(env == "prod" && replicas > 1);
|
||||||
|
safe = env != "dev" || replicas >= 1;
|
||||||
|
}
|
||||||
|
|
@ -51,6 +51,7 @@ export const nav = [
|
||||||
{ title: 'Composition', slug: 'language/expression/composition' },
|
{ title: 'Composition', slug: 'language/expression/composition' },
|
||||||
{ title: 'Default', slug: 'language/expression/default' },
|
{ title: 'Default', slug: 'language/expression/default' },
|
||||||
{ title: 'Arithmetic', slug: 'language/expression/arithmetic' },
|
{ title: 'Arithmetic', slug: 'language/expression/arithmetic' },
|
||||||
|
{ title: 'Logical and Comparison', slug: 'language/expression/logical-and-comparison' },
|
||||||
{ title: 'String Interpolation', slug: 'language/expression/string-interpolation' },
|
{ title: 'String Interpolation', slug: 'language/expression/string-interpolation' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -195,7 +195,7 @@ function isOperatorStart(char) {
|
||||||
|
|
||||||
function readOperator(source, start) {
|
function readOperator(source, start) {
|
||||||
let index = start + 1;
|
let index = start + 1;
|
||||||
if (source[start] === '/' && source[index] === '/') return index + 1;
|
if ((source[start] === '&' || source[start] === '|' || source[start] === '/') && source[index] === source[start]) return index + 1;
|
||||||
if ((source[start] === '<' || source[start] === '>' || source[start] === '=' || source[start] === '!') && source[index] === '=') {
|
if ((source[start] === '<' || source[start] === '>' || source[start] === '=' || source[start] === '!') && source[index] === '=') {
|
||||||
index += 1;
|
index += 1;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ in
|
||||||
schema.Service & {
|
schema.Service & {
|
||||||
name = "api";
|
name = "api";
|
||||||
port = 9000 + 443;
|
port = 9000 + 443;
|
||||||
|
feature.enable = 9000 + 443 > 9000 && true;
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
'schemas/service.dcdl': `Service = {
|
'schemas/service.dcdl': `Service = {
|
||||||
|
|
|
||||||
Binary file not shown.
Loading…
Reference in New Issue
Block a user