Add arithmetic expressions
This commit is contained in:
parent
dc28cddbff
commit
3f7dd7c692
|
|
@ -71,6 +71,10 @@ pub enum Expr {
|
||||||
scrutinee: ExprId,
|
scrutinee: ExprId,
|
||||||
arms: Vec<MatchArm>,
|
arms: Vec<MatchArm>,
|
||||||
},
|
},
|
||||||
|
Unary {
|
||||||
|
op: UnaryOp,
|
||||||
|
expr: ExprId,
|
||||||
|
},
|
||||||
Binary {
|
Binary {
|
||||||
op: BinaryOp,
|
op: BinaryOp,
|
||||||
lhs: ExprId,
|
lhs: ExprId,
|
||||||
|
|
@ -117,8 +121,17 @@ pub enum Literal {
|
||||||
Bool(bool),
|
Bool(bool),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum UnaryOp {
|
||||||
|
Neg,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum BinaryOp {
|
pub enum BinaryOp {
|
||||||
|
Add,
|
||||||
|
Sub,
|
||||||
|
Mul,
|
||||||
|
Div,
|
||||||
And,
|
And,
|
||||||
Patch,
|
Patch,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ use alloc::{format, string::String, vec, vec::Vec};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
ExprId, SourceForm, SourceId, Span,
|
ExprId, SourceForm, SourceId, Span,
|
||||||
ast::{Ast, BinaryOp, CompareOp, Expr, Field, Literal},
|
ast::{Ast, BinaryOp, CompareOp, Expr, Field, Literal, UnaryOp},
|
||||||
constraints::normalize_constraints,
|
constraints::normalize_constraints,
|
||||||
diagnostic::{Diagnostic, DiagnosticKind, Result},
|
diagnostic::{Diagnostic, DiagnosticKind, Result},
|
||||||
embedding::HostValue,
|
embedding::HostValue,
|
||||||
|
|
@ -337,6 +337,18 @@ impl<L: SourceLoader> Engine<L> {
|
||||||
"no match arm matched",
|
"no match arm matched",
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
Expr::Unary { op, expr } => {
|
||||||
|
let value = self.eval_expr(
|
||||||
|
ExprRef {
|
||||||
|
module: reference.module,
|
||||||
|
expr,
|
||||||
|
},
|
||||||
|
env,
|
||||||
|
)?;
|
||||||
|
match op {
|
||||||
|
UnaryOp::Neg => negate_number(value, span),
|
||||||
|
}
|
||||||
|
}
|
||||||
Expr::Binary { op, lhs, rhs } => {
|
Expr::Binary { op, lhs, rhs } => {
|
||||||
let lhs = self.eval_expr(
|
let lhs = self.eval_expr(
|
||||||
ExprRef {
|
ExprRef {
|
||||||
|
|
@ -353,6 +365,10 @@ impl<L: SourceLoader> Engine<L> {
|
||||||
env,
|
env,
|
||||||
)?;
|
)?;
|
||||||
match op {
|
match op {
|
||||||
|
BinaryOp::Add => arithmetic(lhs, rhs, ArithmeticOp::Add, span),
|
||||||
|
BinaryOp::Sub => arithmetic(lhs, rhs, ArithmeticOp::Sub, span),
|
||||||
|
BinaryOp::Mul => arithmetic(lhs, rhs, ArithmeticOp::Mul, span),
|
||||||
|
BinaryOp::Div => arithmetic(lhs, rhs, ArithmeticOp::Div, span),
|
||||||
BinaryOp::And => self.compose_and(lhs, rhs, span),
|
BinaryOp::And => self.compose_and(lhs, rhs, span),
|
||||||
BinaryOp::Patch => self.patch(lhs, rhs),
|
BinaryOp::Patch => self.patch(lhs, rhs),
|
||||||
}
|
}
|
||||||
|
|
@ -999,6 +1015,116 @@ fn satisfies_regex(_value: &RuntimeValue, _pattern: &str, span: Span) -> Result<
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
enum ArithmeticOp {
|
||||||
|
Add,
|
||||||
|
Sub,
|
||||||
|
Mul,
|
||||||
|
Div,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
enum Number {
|
||||||
|
Int(i64),
|
||||||
|
Float(f64),
|
||||||
|
}
|
||||||
|
|
||||||
|
fn negate_number(value: RuntimeValue, span: Span) -> Result<RuntimeValue> {
|
||||||
|
match value {
|
||||||
|
RuntimeValue::Concrete(ConcreteValue::Int(value)) => value
|
||||||
|
.checked_neg()
|
||||||
|
.map(|value| RuntimeValue::Concrete(ConcreteValue::Int(value)))
|
||||||
|
.ok_or_else(|| arithmetic_error(span, "integer negation overflow")),
|
||||||
|
RuntimeValue::Concrete(ConcreteValue::Float(value)) => {
|
||||||
|
Ok(RuntimeValue::Concrete(ConcreteValue::Float(-value)))
|
||||||
|
}
|
||||||
|
_ => Err(Diagnostic::new(
|
||||||
|
DiagnosticKind::TypeMismatch,
|
||||||
|
span,
|
||||||
|
"unary '-' expects a numeric value",
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn arithmetic(
|
||||||
|
lhs: RuntimeValue,
|
||||||
|
rhs: RuntimeValue,
|
||||||
|
op: ArithmeticOp,
|
||||||
|
span: Span,
|
||||||
|
) -> Result<RuntimeValue> {
|
||||||
|
let lhs = number_from_runtime(lhs).ok_or_else(|| arithmetic_type_error(span))?;
|
||||||
|
let rhs = number_from_runtime(rhs).ok_or_else(|| arithmetic_type_error(span))?;
|
||||||
|
match (lhs, rhs) {
|
||||||
|
(Number::Int(lhs), Number::Int(rhs)) => arithmetic_int(lhs, rhs, op, span),
|
||||||
|
(lhs, rhs) => arithmetic_float(number_to_f64(lhs), number_to_f64(rhs), op, span),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn arithmetic_int(lhs: i64, rhs: i64, op: ArithmeticOp, span: Span) -> Result<RuntimeValue> {
|
||||||
|
match op {
|
||||||
|
ArithmeticOp::Add => lhs
|
||||||
|
.checked_add(rhs)
|
||||||
|
.map(|value| RuntimeValue::Concrete(ConcreteValue::Int(value)))
|
||||||
|
.ok_or_else(|| arithmetic_error(span, "integer addition overflow")),
|
||||||
|
ArithmeticOp::Sub => lhs
|
||||||
|
.checked_sub(rhs)
|
||||||
|
.map(|value| RuntimeValue::Concrete(ConcreteValue::Int(value)))
|
||||||
|
.ok_or_else(|| arithmetic_error(span, "integer subtraction overflow")),
|
||||||
|
ArithmeticOp::Mul => lhs
|
||||||
|
.checked_mul(rhs)
|
||||||
|
.map(|value| RuntimeValue::Concrete(ConcreteValue::Int(value)))
|
||||||
|
.ok_or_else(|| arithmetic_error(span, "integer multiplication overflow")),
|
||||||
|
ArithmeticOp::Div => {
|
||||||
|
if rhs == 0 {
|
||||||
|
return Err(arithmetic_error(span, "division by zero"));
|
||||||
|
}
|
||||||
|
Ok(RuntimeValue::Concrete(ConcreteValue::Float(
|
||||||
|
lhs as f64 / rhs as f64,
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn arithmetic_float(lhs: f64, rhs: f64, op: ArithmeticOp, span: Span) -> Result<RuntimeValue> {
|
||||||
|
if matches!(op, ArithmeticOp::Div) && rhs == 0.0 {
|
||||||
|
return Err(arithmetic_error(span, "division by zero"));
|
||||||
|
}
|
||||||
|
let value = match op {
|
||||||
|
ArithmeticOp::Add => lhs + rhs,
|
||||||
|
ArithmeticOp::Sub => lhs - rhs,
|
||||||
|
ArithmeticOp::Mul => lhs * rhs,
|
||||||
|
ArithmeticOp::Div => lhs / rhs,
|
||||||
|
};
|
||||||
|
Ok(RuntimeValue::Concrete(ConcreteValue::Float(value)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn number_from_runtime(value: RuntimeValue) -> Option<Number> {
|
||||||
|
match value {
|
||||||
|
RuntimeValue::Concrete(ConcreteValue::Int(value)) => Some(Number::Int(value)),
|
||||||
|
RuntimeValue::Concrete(ConcreteValue::Float(value)) => Some(Number::Float(value)),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn number_to_f64(value: Number) -> f64 {
|
||||||
|
match value {
|
||||||
|
Number::Int(value) => value as f64,
|
||||||
|
Number::Float(value) => value,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn arithmetic_type_error(span: Span) -> Diagnostic {
|
||||||
|
Diagnostic::new(
|
||||||
|
DiagnosticKind::TypeMismatch,
|
||||||
|
span,
|
||||||
|
"arithmetic operators expect numeric values",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn arithmetic_error(span: Span, message: &'static str) -> Diagnostic {
|
||||||
|
Diagnostic::new(DiagnosticKind::Conflict, span, message)
|
||||||
|
}
|
||||||
|
|
||||||
fn compare_value(value: &RuntimeValue, op: CompareOp, expected: &LiteralValue) -> bool {
|
fn compare_value(value: &RuntimeValue, op: CompareOp, expected: &LiteralValue) -> bool {
|
||||||
match (value, expected) {
|
match (value, expected) {
|
||||||
(RuntimeValue::Concrete(ConcreteValue::Int(actual)), LiteralValue::Int(expected)) => {
|
(RuntimeValue::Concrete(ConcreteValue::Int(actual)), LiteralValue::Int(expected)) => {
|
||||||
|
|
@ -1072,6 +1198,41 @@ mod tests {
|
||||||
assert!(matches!(data, Data::Object(_)));
|
assert!(matches!(data, Data::Object(_)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn evaluates_arithmetic_expressions() {
|
||||||
|
let data = eval_data(
|
||||||
|
r#"
|
||||||
|
{
|
||||||
|
sum = 1 + 2 * 3;
|
||||||
|
diff = 10 - 4;
|
||||||
|
product = (2 + 3) * 4;
|
||||||
|
quotient = 5 / 2;
|
||||||
|
negative = -3 + 1;
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
let Data::Object(fields) = data else { panic!() };
|
||||||
|
assert_eq!(fields[0].value, Data::Int(7));
|
||||||
|
assert_eq!(fields[1].value, Data::Int(6));
|
||||||
|
assert_eq!(fields[2].value, Data::Int(20));
|
||||||
|
assert_eq!(fields[3].value, Data::Float(2.5));
|
||||||
|
assert_eq!(fields[4].value, Data::Int(-2));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn arithmetic_can_feed_constraints() {
|
||||||
|
let data = eval_data("port = Int & > 4000 + 42 default 8080;");
|
||||||
|
let Data::Object(fields) = data else { panic!() };
|
||||||
|
assert_eq!(fields[0].value, Data::Int(8080));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rejects_division_by_zero() {
|
||||||
|
let parsed = parse_source("1 / 0").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(
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,10 @@ pub enum TokenKind {
|
||||||
Equal,
|
Equal,
|
||||||
Arrow,
|
Arrow,
|
||||||
Amp,
|
Amp,
|
||||||
|
Plus,
|
||||||
|
Minus,
|
||||||
|
Star,
|
||||||
|
Slash,
|
||||||
SlashSlash,
|
SlashSlash,
|
||||||
Gt,
|
Gt,
|
||||||
Gte,
|
Gte,
|
||||||
|
|
@ -67,9 +71,13 @@ impl<'a> Lexer<'a> {
|
||||||
|
|
||||||
pub fn tokenize(mut self) -> Result<Vec<Token>> {
|
pub fn tokenize(mut self) -> Result<Vec<Token>> {
|
||||||
let mut tokens = Vec::new();
|
let mut tokens = Vec::new();
|
||||||
|
let mut previous = None;
|
||||||
loop {
|
loop {
|
||||||
let token = self.next_token()?;
|
let token = self.next_token(previous.as_ref())?;
|
||||||
let is_eof = token.kind == TokenKind::Eof;
|
let is_eof = token.kind == TokenKind::Eof;
|
||||||
|
if !is_eof {
|
||||||
|
previous = Some(token.kind.clone());
|
||||||
|
}
|
||||||
tokens.push(token);
|
tokens.push(token);
|
||||||
if is_eof {
|
if is_eof {
|
||||||
return Ok(tokens);
|
return Ok(tokens);
|
||||||
|
|
@ -77,7 +85,7 @@ impl<'a> Lexer<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn next_token(&mut self) -> Result<Token> {
|
fn next_token(&mut self, previous: Option<&TokenKind>) -> Result<Token> {
|
||||||
self.skip_ws_and_comments();
|
self.skip_ws_and_comments();
|
||||||
let start = self.pos;
|
let start = self.pos;
|
||||||
let Some(ch) = self.peek() else {
|
let Some(ch) = self.peek() else {
|
||||||
|
|
@ -136,6 +144,18 @@ impl<'a> Lexer<'a> {
|
||||||
self.pos += 1;
|
self.pos += 1;
|
||||||
TokenKind::Amp
|
TokenKind::Amp
|
||||||
}
|
}
|
||||||
|
b'+' => {
|
||||||
|
self.pos += 1;
|
||||||
|
TokenKind::Plus
|
||||||
|
}
|
||||||
|
b'-' => {
|
||||||
|
self.pos += 1;
|
||||||
|
TokenKind::Minus
|
||||||
|
}
|
||||||
|
b'*' => {
|
||||||
|
self.pos += 1;
|
||||||
|
TokenKind::Star
|
||||||
|
}
|
||||||
b'=' => {
|
b'=' => {
|
||||||
self.pos += 1;
|
self.pos += 1;
|
||||||
if self.consume(b'>') {
|
if self.consume(b'>') {
|
||||||
|
|
@ -164,6 +184,8 @@ impl<'a> Lexer<'a> {
|
||||||
self.pos += 1;
|
self.pos += 1;
|
||||||
if self.consume(b'/') {
|
if self.consume(b'/') {
|
||||||
TokenKind::SlashSlash
|
TokenKind::SlashSlash
|
||||||
|
} else if previous.is_some_and(token_can_end_expr) {
|
||||||
|
TokenKind::Slash
|
||||||
} else {
|
} else {
|
||||||
self.lex_regex(start)?
|
self.lex_regex(start)?
|
||||||
}
|
}
|
||||||
|
|
@ -341,6 +363,23 @@ fn is_ident_continue(c: u8) -> bool {
|
||||||
c.is_ascii_alphanumeric() || c == b'_'
|
c.is_ascii_alphanumeric() || c == b'_'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn token_can_end_expr(kind: &TokenKind) -> bool {
|
||||||
|
matches!(
|
||||||
|
kind,
|
||||||
|
TokenKind::Ident(_)
|
||||||
|
| TokenKind::Int(_)
|
||||||
|
| TokenKind::Float(_)
|
||||||
|
| TokenKind::String(_)
|
||||||
|
| TokenKind::Regex(_)
|
||||||
|
| TokenKind::True
|
||||||
|
| TokenKind::False
|
||||||
|
| TokenKind::Underscore
|
||||||
|
| TokenKind::RBrace
|
||||||
|
| TokenKind::RBracket
|
||||||
|
| TokenKind::RParen
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ use alloc::{string::String, vec::Vec};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
SourceId, Span,
|
SourceId, Span,
|
||||||
ast::{Ast, BinaryOp, CompareOp, Expr, ExprId, Field, Literal, MatchArm, Param},
|
ast::{Ast, BinaryOp, CompareOp, Expr, ExprId, Field, Literal, MatchArm, Param, UnaryOp},
|
||||||
diagnostic::{Diagnostic, Result},
|
diagnostic::{Diagnostic, Result},
|
||||||
lexer::{Lexer, Token, TokenKind},
|
lexer::{Lexer, Token, TokenKind},
|
||||||
};
|
};
|
||||||
|
|
@ -115,6 +115,38 @@ impl Parser {
|
||||||
let rhs = self.parse_expr(r_bp)?;
|
let rhs = self.parse_expr(r_bp)?;
|
||||||
let span = self.ast.span(lhs).join(self.ast.span(rhs)).join(op_span);
|
let span = self.ast.span(lhs).join(self.ast.span(rhs)).join(op_span);
|
||||||
lhs = match kind {
|
lhs = match kind {
|
||||||
|
InfixKind::Add => self.ast.push(
|
||||||
|
Expr::Binary {
|
||||||
|
op: BinaryOp::Add,
|
||||||
|
lhs,
|
||||||
|
rhs,
|
||||||
|
},
|
||||||
|
span,
|
||||||
|
),
|
||||||
|
InfixKind::Sub => self.ast.push(
|
||||||
|
Expr::Binary {
|
||||||
|
op: BinaryOp::Sub,
|
||||||
|
lhs,
|
||||||
|
rhs,
|
||||||
|
},
|
||||||
|
span,
|
||||||
|
),
|
||||||
|
InfixKind::Mul => self.ast.push(
|
||||||
|
Expr::Binary {
|
||||||
|
op: BinaryOp::Mul,
|
||||||
|
lhs,
|
||||||
|
rhs,
|
||||||
|
},
|
||||||
|
span,
|
||||||
|
),
|
||||||
|
InfixKind::Div => self.ast.push(
|
||||||
|
Expr::Binary {
|
||||||
|
op: BinaryOp::Div,
|
||||||
|
lhs,
|
||||||
|
rhs,
|
||||||
|
},
|
||||||
|
span,
|
||||||
|
),
|
||||||
InfixKind::And => self.ast.push(
|
InfixKind::And => self.ast.push(
|
||||||
Expr::Binary {
|
Expr::Binary {
|
||||||
op: BinaryOp::And,
|
op: BinaryOp::And,
|
||||||
|
|
@ -173,6 +205,17 @@ impl Parser {
|
||||||
TokenKind::Let => self.parse_let(token.span),
|
TokenKind::Let => self.parse_let(token.span),
|
||||||
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 => {
|
||||||
|
let expr = self.parse_expr(11)?;
|
||||||
|
let span = token.span.join(self.ast.span(expr));
|
||||||
|
Ok(self.ast.push(
|
||||||
|
Expr::Unary {
|
||||||
|
op: UnaryOp::Neg,
|
||||||
|
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,
|
||||||
|
|
@ -181,7 +224,7 @@ impl Parser {
|
||||||
TokenKind::Lte => CompareOp::Lte,
|
TokenKind::Lte => CompareOp::Lte,
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
let value = self.parse_expr(8)?;
|
let value = self.parse_expr(6)?;
|
||||||
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))
|
||||||
}
|
}
|
||||||
|
|
@ -406,6 +449,10 @@ 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::Minus => Some((InfixKind::Sub, 7, 8)),
|
||||||
|
TokenKind::Star => Some((InfixKind::Mul, 9, 10)),
|
||||||
|
TokenKind::Slash => Some((InfixKind::Div, 9, 10)),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -498,6 +545,10 @@ impl Parser {
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
enum InfixKind {
|
enum InfixKind {
|
||||||
|
Add,
|
||||||
|
Sub,
|
||||||
|
Mul,
|
||||||
|
Div,
|
||||||
And,
|
And,
|
||||||
Patch,
|
Patch,
|
||||||
Default,
|
Default,
|
||||||
|
|
|
||||||
43
doc/manual/souce/language/expression/arithmetic.md
Normal file
43
doc/manual/souce/language/expression/arithmetic.md
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
# Arithmetic Expression
|
||||||
|
|
||||||
|
Decodal supports arithmetic over concrete numeric values.
|
||||||
|
|
||||||
|
```dcdl
|
||||||
|
{
|
||||||
|
workers = 2 + 2;
|
||||||
|
timeout = 30.0 / 2;
|
||||||
|
port = 8000 + 80;
|
||||||
|
negative = -1;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Operators
|
||||||
|
|
||||||
|
- `+` addition
|
||||||
|
- `-` subtraction
|
||||||
|
- `*` multiplication
|
||||||
|
- `/` division
|
||||||
|
- unary `-` negation
|
||||||
|
|
||||||
|
`*` and `/` bind tighter than `+` and `-`.
|
||||||
|
Parentheses can be used to make grouping explicit.
|
||||||
|
|
||||||
|
```dcdl
|
||||||
|
2 + 3 * 4 # 14
|
||||||
|
(2 + 3) * 4 # 20
|
||||||
|
```
|
||||||
|
|
||||||
|
## Numeric behavior
|
||||||
|
|
||||||
|
Arithmetic requires concrete `Int` or `Float` operands.
|
||||||
|
`Int + Int`, `Int - Int`, and `Int * Int` produce `Int` when no overflow occurs.
|
||||||
|
Mixed `Int` / `Float` arithmetic produces `Float`.
|
||||||
|
Division always produces `Float`.
|
||||||
|
|
||||||
|
Division by zero and integer overflow are evaluation errors.
|
||||||
|
|
||||||
|
Arithmetic expressions can be used anywhere a concrete numeric expression is expected, including defaults and numeric constraints.
|
||||||
|
|
||||||
|
```dcdl
|
||||||
|
port = Int & > 4000 + 42 default 8080;
|
||||||
|
```
|
||||||
|
|
@ -1,6 +1,26 @@
|
||||||
# 合成演算子
|
# 演算子
|
||||||
|
|
||||||
この章では、`&` と `//` の意味を定義する。
|
この章では、Decodal の演算子の意味を定義する。
|
||||||
|
|
||||||
|
## 優先順位
|
||||||
|
|
||||||
|
優先順位は高い順に以下である。
|
||||||
|
|
||||||
|
1. 関数呼び出しとフィールド参照
|
||||||
|
2. unary `-`
|
||||||
|
3. `*` `/`
|
||||||
|
4. `+` `-`
|
||||||
|
5. `&`
|
||||||
|
6. `//`
|
||||||
|
7. `default`
|
||||||
|
|
||||||
|
同じ優先順位の二項演算子は左結合である。
|
||||||
|
`default` は右結合である。
|
||||||
|
|
||||||
|
## Arithmetic operators
|
||||||
|
|
||||||
|
`+` `-` `*` `/` は具体的な `Int` / `Float` に対する四則演算である。
|
||||||
|
詳しくは [Arithmetic Expression](./expression/arithmetic.md) を参照する。
|
||||||
|
|
||||||
## `&`: 制約合成
|
## `&`: 制約合成
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -115,6 +115,7 @@ rec
|
||||||
主要な演算子は以下である。
|
主要な演算子は以下である。
|
||||||
|
|
||||||
```text
|
```text
|
||||||
|
+ - * / 四則演算
|
||||||
& 制約合成
|
& 制約合成
|
||||||
// patch 合成
|
// patch 合成
|
||||||
default fallback 指定
|
default fallback 指定
|
||||||
|
|
@ -122,5 +123,4 @@ default fallback 指定
|
||||||
. フィールド参照 / ドットパス定義
|
. フィールド参照 / ドットパス定義
|
||||||
```
|
```
|
||||||
|
|
||||||
演算子の優先順位は未確定である。
|
演算子の優先順位は [合成演算子](./operators.md) で定義する。
|
||||||
詳細は [合成演算子](./operators.md) で定義する。
|
|
||||||
|
|
|
||||||
|
|
@ -74,3 +74,31 @@ base // {
|
||||||
body: (literal (string)))
|
body: (literal (string)))
|
||||||
(match_arm
|
(match_arm
|
||||||
body: (literal (string))))))))
|
body: (literal (string))))))))
|
||||||
|
|
||||||
|
==================
|
||||||
|
Arithmetic
|
||||||
|
==================
|
||||||
|
{
|
||||||
|
value = 1 + 2 * 3;
|
||||||
|
grouped = (1 + 2) / -3;
|
||||||
|
}
|
||||||
|
---
|
||||||
|
|
||||||
|
(source_file
|
||||||
|
(object
|
||||||
|
(field_definition
|
||||||
|
path: (field_path (identifier))
|
||||||
|
value: (binary_expression
|
||||||
|
left: (literal (integer))
|
||||||
|
right: (binary_expression
|
||||||
|
left: (literal (integer))
|
||||||
|
right: (literal (integer)))))
|
||||||
|
(field_definition
|
||||||
|
path: (field_path (identifier))
|
||||||
|
value: (binary_expression
|
||||||
|
left: (parenthesized_expression
|
||||||
|
(binary_expression
|
||||||
|
left: (literal (integer))
|
||||||
|
right: (literal (integer))))
|
||||||
|
right: (unary_expression
|
||||||
|
operand: (literal (integer)))))))
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,9 @@ const PREC = {
|
||||||
DEFAULT: 1,
|
DEFAULT: 1,
|
||||||
PATCH: 2,
|
PATCH: 2,
|
||||||
AND: 3,
|
AND: 3,
|
||||||
|
ADD: 4,
|
||||||
|
MUL: 5,
|
||||||
|
UNARY: 6,
|
||||||
CALL: 7,
|
CALL: 7,
|
||||||
PATH: 8,
|
PATH: 8,
|
||||||
};
|
};
|
||||||
|
|
@ -52,6 +55,7 @@ module.exports = grammar({
|
||||||
$.parenthesized_expression,
|
$.parenthesized_expression,
|
||||||
$.call_expression,
|
$.call_expression,
|
||||||
$.path_expression,
|
$.path_expression,
|
||||||
|
$.unary_expression,
|
||||||
$.binary_expression,
|
$.binary_expression,
|
||||||
$.default_expression,
|
$.default_expression,
|
||||||
),
|
),
|
||||||
|
|
@ -161,10 +165,25 @@ module.exports = grammar({
|
||||||
|
|
||||||
comparison_constraint: $ => prec(6, seq(
|
comparison_constraint: $ => prec(6, seq(
|
||||||
field('operator', choice('>', '>=', '<', '<=')),
|
field('operator', choice('>', '>=', '<', '<=')),
|
||||||
field('value', choice($.integer, $.float)),
|
field('value', $._expression),
|
||||||
|
)),
|
||||||
|
|
||||||
|
unary_expression: $ => prec(PREC.UNARY, seq(
|
||||||
|
field('operator', '-'),
|
||||||
|
field('operand', $._expression),
|
||||||
)),
|
)),
|
||||||
|
|
||||||
binary_expression: $ => choice(
|
binary_expression: $ => choice(
|
||||||
|
prec.left(PREC.ADD, seq(
|
||||||
|
field('left', $._expression),
|
||||||
|
field('operator', choice('+', '-')),
|
||||||
|
field('right', $._expression),
|
||||||
|
)),
|
||||||
|
prec.left(PREC.MUL, seq(
|
||||||
|
field('left', $._expression),
|
||||||
|
field('operator', choice('*', '/')),
|
||||||
|
field('right', $._expression),
|
||||||
|
)),
|
||||||
prec.left(PREC.AND, seq(
|
prec.left(PREC.AND, seq(
|
||||||
field('left', $._expression),
|
field('left', $._expression),
|
||||||
field('operator', '&'),
|
field('operator', '&'),
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,10 @@
|
||||||
[
|
[
|
||||||
"&"
|
"&"
|
||||||
"//"
|
"//"
|
||||||
|
"+"
|
||||||
|
"-"
|
||||||
|
"*"
|
||||||
|
"/"
|
||||||
"=>"
|
"=>"
|
||||||
"="
|
"="
|
||||||
">"
|
">"
|
||||||
|
|
|
||||||
120
editors/tree-sitter-decodal/src/grammar.json
generated
120
editors/tree-sitter-decodal/src/grammar.json
generated
|
|
@ -127,6 +127,10 @@
|
||||||
"type": "SYMBOL",
|
"type": "SYMBOL",
|
||||||
"name": "path_expression"
|
"name": "path_expression"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "SYMBOL",
|
||||||
|
"name": "unary_expression"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "SYMBOL",
|
"type": "SYMBOL",
|
||||||
"name": "binary_expression"
|
"name": "binary_expression"
|
||||||
|
|
@ -844,18 +848,34 @@
|
||||||
"type": "FIELD",
|
"type": "FIELD",
|
||||||
"name": "value",
|
"name": "value",
|
||||||
"content": {
|
"content": {
|
||||||
"type": "CHOICE",
|
|
||||||
"members": [
|
|
||||||
{
|
|
||||||
"type": "SYMBOL",
|
"type": "SYMBOL",
|
||||||
"name": "integer"
|
"name": "_expression"
|
||||||
},
|
}
|
||||||
{
|
|
||||||
"type": "SYMBOL",
|
|
||||||
"name": "float"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"unary_expression": {
|
||||||
|
"type": "PREC",
|
||||||
|
"value": 6,
|
||||||
|
"content": {
|
||||||
|
"type": "SEQ",
|
||||||
|
"members": [
|
||||||
|
{
|
||||||
|
"type": "FIELD",
|
||||||
|
"name": "operator",
|
||||||
|
"content": {
|
||||||
|
"type": "STRING",
|
||||||
|
"value": "-"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "FIELD",
|
||||||
|
"name": "operand",
|
||||||
|
"content": {
|
||||||
|
"type": "SYMBOL",
|
||||||
|
"name": "_expression"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -863,6 +883,90 @@
|
||||||
"binary_expression": {
|
"binary_expression": {
|
||||||
"type": "CHOICE",
|
"type": "CHOICE",
|
||||||
"members": [
|
"members": [
|
||||||
|
{
|
||||||
|
"type": "PREC_LEFT",
|
||||||
|
"value": 4,
|
||||||
|
"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": "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": "CHOICE",
|
||||||
|
"members": [
|
||||||
|
{
|
||||||
|
"type": "STRING",
|
||||||
|
"value": "*"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "STRING",
|
||||||
|
"value": "/"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "FIELD",
|
||||||
|
"name": "right",
|
||||||
|
"content": {
|
||||||
|
"type": "SYMBOL",
|
||||||
|
"name": "_expression"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "PREC_LEFT",
|
"type": "PREC_LEFT",
|
||||||
"value": 3,
|
"value": 3,
|
||||||
|
|
|
||||||
246
editors/tree-sitter-decodal/src/node-types.json
generated
246
editors/tree-sitter-decodal/src/node-types.json
generated
|
|
@ -66,6 +66,10 @@
|
||||||
{
|
{
|
||||||
"type": "regex_literal",
|
"type": "regex_literal",
|
||||||
"named": true
|
"named": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "unary_expression",
|
||||||
|
"named": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -137,6 +141,10 @@
|
||||||
{
|
{
|
||||||
"type": "regex_literal",
|
"type": "regex_literal",
|
||||||
"named": true
|
"named": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "unary_expression",
|
||||||
|
"named": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
@ -148,6 +156,22 @@
|
||||||
"type": "&",
|
"type": "&",
|
||||||
"named": false
|
"named": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "*",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "+",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "-",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "/",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "//",
|
"type": "//",
|
||||||
"named": false
|
"named": false
|
||||||
|
|
@ -217,6 +241,10 @@
|
||||||
{
|
{
|
||||||
"type": "regex_literal",
|
"type": "regex_literal",
|
||||||
"named": true
|
"named": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "unary_expression",
|
||||||
|
"named": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -294,6 +322,10 @@
|
||||||
{
|
{
|
||||||
"type": "regex_literal",
|
"type": "regex_literal",
|
||||||
"named": true
|
"named": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "unary_expression",
|
||||||
|
"named": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -361,6 +393,10 @@
|
||||||
{
|
{
|
||||||
"type": "regex_literal",
|
"type": "regex_literal",
|
||||||
"named": true
|
"named": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "unary_expression",
|
||||||
|
"named": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -396,11 +432,67 @@
|
||||||
"required": true,
|
"required": true,
|
||||||
"types": [
|
"types": [
|
||||||
{
|
{
|
||||||
"type": "float",
|
"type": "array",
|
||||||
"named": true
|
"named": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "integer",
|
"type": "binary_expression",
|
||||||
|
"named": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "call_expression",
|
||||||
|
"named": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "comparison_constraint",
|
||||||
|
"named": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "default_expression",
|
||||||
|
"named": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "function_expression",
|
||||||
|
"named": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "identifier",
|
||||||
|
"named": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "import_expression",
|
||||||
|
"named": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "let_expression",
|
||||||
|
"named": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "literal",
|
||||||
|
"named": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "match_expression",
|
||||||
|
"named": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"named": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "parenthesized_expression",
|
||||||
|
"named": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "path_expression",
|
||||||
|
"named": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "regex_literal",
|
||||||
|
"named": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "unary_expression",
|
||||||
"named": true
|
"named": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
@ -474,6 +566,10 @@
|
||||||
{
|
{
|
||||||
"type": "regex_literal",
|
"type": "regex_literal",
|
||||||
"named": true
|
"named": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "unary_expression",
|
||||||
|
"named": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
@ -540,6 +636,10 @@
|
||||||
{
|
{
|
||||||
"type": "regex_literal",
|
"type": "regex_literal",
|
||||||
"named": true
|
"named": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "unary_expression",
|
||||||
|
"named": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -622,6 +722,10 @@
|
||||||
{
|
{
|
||||||
"type": "regex_literal",
|
"type": "regex_literal",
|
||||||
"named": true
|
"named": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "unary_expression",
|
||||||
|
"named": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -709,6 +813,10 @@
|
||||||
{
|
{
|
||||||
"type": "regex_literal",
|
"type": "regex_literal",
|
||||||
"named": true
|
"named": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "unary_expression",
|
||||||
|
"named": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -807,6 +915,10 @@
|
||||||
{
|
{
|
||||||
"type": "regex_literal",
|
"type": "regex_literal",
|
||||||
"named": true
|
"named": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "unary_expression",
|
||||||
|
"named": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -916,6 +1028,10 @@
|
||||||
{
|
{
|
||||||
"type": "regex_literal",
|
"type": "regex_literal",
|
||||||
"named": true
|
"named": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "unary_expression",
|
||||||
|
"named": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
@ -986,6 +1102,10 @@
|
||||||
{
|
{
|
||||||
"type": "regex_literal",
|
"type": "regex_literal",
|
||||||
"named": true
|
"named": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "unary_expression",
|
||||||
|
"named": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -1058,6 +1178,10 @@
|
||||||
{
|
{
|
||||||
"type": "regex_literal",
|
"type": "regex_literal",
|
||||||
"named": true
|
"named": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "unary_expression",
|
||||||
|
"named": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -1155,6 +1279,10 @@
|
||||||
{
|
{
|
||||||
"type": "regex_literal",
|
"type": "regex_literal",
|
||||||
"named": true
|
"named": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "unary_expression",
|
||||||
|
"named": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
@ -1237,6 +1365,10 @@
|
||||||
{
|
{
|
||||||
"type": "regex_literal",
|
"type": "regex_literal",
|
||||||
"named": true
|
"named": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "unary_expression",
|
||||||
|
"named": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -1318,6 +1450,10 @@
|
||||||
{
|
{
|
||||||
"type": "regex_literal",
|
"type": "regex_literal",
|
||||||
"named": true
|
"named": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "unary_expression",
|
||||||
|
"named": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -1394,10 +1530,100 @@
|
||||||
{
|
{
|
||||||
"type": "regex_literal",
|
"type": "regex_literal",
|
||||||
"named": true
|
"named": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "unary_expression",
|
||||||
|
"named": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "unary_expression",
|
||||||
|
"named": true,
|
||||||
|
"fields": {
|
||||||
|
"operand": {
|
||||||
|
"multiple": false,
|
||||||
|
"required": true,
|
||||||
|
"types": [
|
||||||
|
{
|
||||||
|
"type": "array",
|
||||||
|
"named": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "binary_expression",
|
||||||
|
"named": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "call_expression",
|
||||||
|
"named": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "comparison_constraint",
|
||||||
|
"named": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "default_expression",
|
||||||
|
"named": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "function_expression",
|
||||||
|
"named": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "identifier",
|
||||||
|
"named": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "import_expression",
|
||||||
|
"named": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "let_expression",
|
||||||
|
"named": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "literal",
|
||||||
|
"named": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "match_expression",
|
||||||
|
"named": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"named": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "parenthesized_expression",
|
||||||
|
"named": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "path_expression",
|
||||||
|
"named": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "regex_literal",
|
||||||
|
"named": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "unary_expression",
|
||||||
|
"named": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"operator": {
|
||||||
|
"multiple": false,
|
||||||
|
"required": true,
|
||||||
|
"types": [
|
||||||
|
{
|
||||||
|
"type": "-",
|
||||||
|
"named": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "&",
|
"type": "&",
|
||||||
"named": false
|
"named": false
|
||||||
|
|
@ -1410,14 +1636,30 @@
|
||||||
"type": ")",
|
"type": ")",
|
||||||
"named": false
|
"named": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "*",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "+",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": ",",
|
"type": ",",
|
||||||
"named": false
|
"named": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "-",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": ".",
|
"type": ".",
|
||||||
"named": false
|
"named": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "/",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "//",
|
"type": "//",
|
||||||
"named": false
|
"named": false
|
||||||
|
|
|
||||||
8300
editors/tree-sitter-decodal/src/parser.c
generated
8300
editors/tree-sitter-decodal/src/parser.c
generated
File diff suppressed because it is too large
Load Diff
7
examples/arithmetic.dcdl
Normal file
7
examples/arithmetic.dcdl
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
workers = 2 + 2;
|
||||||
|
memory_gib = 1.5 * 4;
|
||||||
|
port = 9000 + 443;
|
||||||
|
timeout_seconds = 30 / 2;
|
||||||
|
negative_offset = -3;
|
||||||
|
}
|
||||||
|
|
@ -50,6 +50,7 @@ export const nav = [
|
||||||
{ title: 'Import', slug: 'language/expression/import' },
|
{ title: 'Import', slug: 'language/expression/import' },
|
||||||
{ 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: 'String Interpolation', slug: 'language/expression/string-interpolation' },
|
{ title: 'String Interpolation', slug: 'language/expression/string-interpolation' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -28,12 +28,13 @@ export function highlightCode(code, language = '') {
|
||||||
export function highlightDecodal(source) {
|
export function highlightDecodal(source) {
|
||||||
let html = '';
|
let html = '';
|
||||||
let index = 0;
|
let index = 0;
|
||||||
|
let canEndExpression = false;
|
||||||
|
|
||||||
while (index < source.length) {
|
while (index < source.length) {
|
||||||
const char = source[index];
|
const char = source[index];
|
||||||
const next = source[index + 1];
|
const next = source[index + 1];
|
||||||
|
|
||||||
if (char === '/' && next === '/') {
|
if (char === '#') {
|
||||||
const end = readUntilLineEnd(source, index);
|
const end = readUntilLineEnd(source, index);
|
||||||
html += token('comment', source.slice(index, end));
|
html += token('comment', source.slice(index, end));
|
||||||
index = end;
|
index = end;
|
||||||
|
|
@ -44,13 +45,15 @@ export function highlightDecodal(source) {
|
||||||
const end = readString(source, index);
|
const end = readString(source, index);
|
||||||
html += token('string', source.slice(index, end));
|
html += token('string', source.slice(index, end));
|
||||||
index = end;
|
index = end;
|
||||||
|
canEndExpression = true;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (char === '/' && next && next !== '/') {
|
if (char === '/' && next && next !== '/' && !canEndExpression) {
|
||||||
const end = readRegex(source, index);
|
const end = readRegex(source, index);
|
||||||
html += token('regex', source.slice(index, end));
|
html += token('regex', source.slice(index, end));
|
||||||
index = end;
|
index = end;
|
||||||
|
canEndExpression = true;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -58,6 +61,7 @@ export function highlightDecodal(source) {
|
||||||
const end = readNumber(source, index);
|
const end = readNumber(source, index);
|
||||||
html += token('number', source.slice(index, end));
|
html += token('number', source.slice(index, end));
|
||||||
index = end;
|
index = end;
|
||||||
|
canEndExpression = true;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -68,6 +72,7 @@ export function highlightDecodal(source) {
|
||||||
else if (DECODAL_TYPES.has(ident)) html += token('type', ident);
|
else if (DECODAL_TYPES.has(ident)) html += token('type', ident);
|
||||||
else if (DECODAL_LITERALS.has(ident)) html += token('literal', ident);
|
else if (DECODAL_LITERALS.has(ident)) html += token('literal', ident);
|
||||||
else html += escapeHtml(ident);
|
else html += escapeHtml(ident);
|
||||||
|
canEndExpression = true;
|
||||||
index = end;
|
index = end;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
@ -75,6 +80,7 @@ export function highlightDecodal(source) {
|
||||||
if (isOperatorStart(char)) {
|
if (isOperatorStart(char)) {
|
||||||
const end = readOperator(source, index);
|
const end = readOperator(source, index);
|
||||||
html += token('operator', source.slice(index, end));
|
html += token('operator', source.slice(index, end));
|
||||||
|
canEndExpression = /[})\]]/.test(char);
|
||||||
index = end;
|
index = end;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
@ -184,11 +190,12 @@ function readIdentifier(source, start) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function isOperatorStart(char) {
|
function isOperatorStart(char) {
|
||||||
return /[&=<>!|:;,.{}()[\]]/.test(char ?? '');
|
return '&=<>!|:;,.{}()[]+-*/'.includes(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[start] === '!') && source[index] === '=') {
|
if ((source[start] === '<' || source[start] === '>' || source[start] === '=' || source[start] === '!') && source[index] === '=') {
|
||||||
index += 1;
|
index += 1;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ const starterFiles = {
|
||||||
in
|
in
|
||||||
schema.Service & {
|
schema.Service & {
|
||||||
name = "api";
|
name = "api";
|
||||||
port = 9443;
|
port = 9000 + 443;
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
'schemas/service.dcdl': `Service = {
|
'schemas/service.dcdl': `Service = {
|
||||||
|
|
|
||||||
Binary file not shown.
Loading…
Reference in New Issue
Block a user