Add arithmetic expressions
This commit is contained in:
parent
dc28cddbff
commit
3f7dd7c692
|
|
@ -71,6 +71,10 @@ pub enum Expr {
|
|||
scrutinee: ExprId,
|
||||
arms: Vec<MatchArm>,
|
||||
},
|
||||
Unary {
|
||||
op: UnaryOp,
|
||||
expr: ExprId,
|
||||
},
|
||||
Binary {
|
||||
op: BinaryOp,
|
||||
lhs: ExprId,
|
||||
|
|
@ -117,8 +121,17 @@ pub enum Literal {
|
|||
Bool(bool),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum UnaryOp {
|
||||
Neg,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum BinaryOp {
|
||||
Add,
|
||||
Sub,
|
||||
Mul,
|
||||
Div,
|
||||
And,
|
||||
Patch,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use alloc::{format, string::String, vec, vec::Vec};
|
|||
|
||||
use crate::{
|
||||
ExprId, SourceForm, SourceId, Span,
|
||||
ast::{Ast, BinaryOp, CompareOp, Expr, Field, Literal},
|
||||
ast::{Ast, BinaryOp, CompareOp, Expr, Field, Literal, UnaryOp},
|
||||
constraints::normalize_constraints,
|
||||
diagnostic::{Diagnostic, DiagnosticKind, Result},
|
||||
embedding::HostValue,
|
||||
|
|
@ -337,6 +337,18 @@ impl<L: SourceLoader> Engine<L> {
|
|||
"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 } => {
|
||||
let lhs = self.eval_expr(
|
||||
ExprRef {
|
||||
|
|
@ -353,6 +365,10 @@ impl<L: SourceLoader> Engine<L> {
|
|||
env,
|
||||
)?;
|
||||
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::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 {
|
||||
match (value, expected) {
|
||||
(RuntimeValue::Concrete(ConcreteValue::Int(actual)), LiteralValue::Int(expected)) => {
|
||||
|
|
@ -1072,6 +1198,41 @@ mod tests {
|
|||
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]
|
||||
fn composes_schema_and_value() {
|
||||
let data = eval_data(
|
||||
|
|
|
|||
|
|
@ -36,6 +36,10 @@ pub enum TokenKind {
|
|||
Equal,
|
||||
Arrow,
|
||||
Amp,
|
||||
Plus,
|
||||
Minus,
|
||||
Star,
|
||||
Slash,
|
||||
SlashSlash,
|
||||
Gt,
|
||||
Gte,
|
||||
|
|
@ -67,9 +71,13 @@ impl<'a> Lexer<'a> {
|
|||
|
||||
pub fn tokenize(mut self) -> Result<Vec<Token>> {
|
||||
let mut tokens = Vec::new();
|
||||
let mut previous = None;
|
||||
loop {
|
||||
let token = self.next_token()?;
|
||||
let token = self.next_token(previous.as_ref())?;
|
||||
let is_eof = token.kind == TokenKind::Eof;
|
||||
if !is_eof {
|
||||
previous = Some(token.kind.clone());
|
||||
}
|
||||
tokens.push(token);
|
||||
if is_eof {
|
||||
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();
|
||||
let start = self.pos;
|
||||
let Some(ch) = self.peek() else {
|
||||
|
|
@ -136,6 +144,18 @@ impl<'a> Lexer<'a> {
|
|||
self.pos += 1;
|
||||
TokenKind::Amp
|
||||
}
|
||||
b'+' => {
|
||||
self.pos += 1;
|
||||
TokenKind::Plus
|
||||
}
|
||||
b'-' => {
|
||||
self.pos += 1;
|
||||
TokenKind::Minus
|
||||
}
|
||||
b'*' => {
|
||||
self.pos += 1;
|
||||
TokenKind::Star
|
||||
}
|
||||
b'=' => {
|
||||
self.pos += 1;
|
||||
if self.consume(b'>') {
|
||||
|
|
@ -164,6 +184,8 @@ impl<'a> Lexer<'a> {
|
|||
self.pos += 1;
|
||||
if self.consume(b'/') {
|
||||
TokenKind::SlashSlash
|
||||
} else if previous.is_some_and(token_can_end_expr) {
|
||||
TokenKind::Slash
|
||||
} else {
|
||||
self.lex_regex(start)?
|
||||
}
|
||||
|
|
@ -341,6 +363,23 @@ fn is_ident_continue(c: u8) -> bool {
|
|||
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)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use alloc::{string::String, vec::Vec};
|
|||
|
||||
use crate::{
|
||||
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},
|
||||
lexer::{Lexer, Token, TokenKind},
|
||||
};
|
||||
|
|
@ -115,6 +115,38 @@ impl Parser {
|
|||
let rhs = self.parse_expr(r_bp)?;
|
||||
let span = self.ast.span(lhs).join(self.ast.span(rhs)).join(op_span);
|
||||
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(
|
||||
Expr::Binary {
|
||||
op: BinaryOp::And,
|
||||
|
|
@ -173,6 +205,17 @@ impl Parser {
|
|||
TokenKind::Let => self.parse_let(token.span),
|
||||
TokenKind::Match => self.parse_match(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 => {
|
||||
let op = match token.kind {
|
||||
TokenKind::Gt => CompareOp::Gt,
|
||||
|
|
@ -181,7 +224,7 @@ impl Parser {
|
|||
TokenKind::Lte => CompareOp::Lte,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let value = self.parse_expr(8)?;
|
||||
let value = self.parse_expr(6)?;
|
||||
let span = token.span.join(self.ast.span(value));
|
||||
Ok(self.ast.push(Expr::CompareConstraint { op, value }, span))
|
||||
}
|
||||
|
|
@ -406,6 +449,10 @@ impl Parser {
|
|||
TokenKind::Default => Some((InfixKind::Default, 1, 2)),
|
||||
TokenKind::SlashSlash => Some((InfixKind::Patch, 3, 4)),
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
|
@ -498,6 +545,10 @@ impl Parser {
|
|||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
enum InfixKind {
|
||||
Add,
|
||||
Sub,
|
||||
Mul,
|
||||
Div,
|
||||
And,
|
||||
Patch,
|
||||
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
|
||||
+ - * / 四則演算
|
||||
& 制約合成
|
||||
// patch 合成
|
||||
default fallback 指定
|
||||
|
|
@ -122,5 +123,4 @@ default fallback 指定
|
|||
. フィールド参照 / ドットパス定義
|
||||
```
|
||||
|
||||
演算子の優先順位は未確定である。
|
||||
詳細は [合成演算子](./operators.md) で定義する。
|
||||
演算子の優先順位は [合成演算子](./operators.md) で定義する。
|
||||
|
|
|
|||
|
|
@ -74,3 +74,31 @@ base // {
|
|||
body: (literal (string)))
|
||||
(match_arm
|
||||
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,
|
||||
PATCH: 2,
|
||||
AND: 3,
|
||||
ADD: 4,
|
||||
MUL: 5,
|
||||
UNARY: 6,
|
||||
CALL: 7,
|
||||
PATH: 8,
|
||||
};
|
||||
|
|
@ -52,6 +55,7 @@ module.exports = grammar({
|
|||
$.parenthesized_expression,
|
||||
$.call_expression,
|
||||
$.path_expression,
|
||||
$.unary_expression,
|
||||
$.binary_expression,
|
||||
$.default_expression,
|
||||
),
|
||||
|
|
@ -161,10 +165,25 @@ module.exports = grammar({
|
|||
|
||||
comparison_constraint: $ => prec(6, seq(
|
||||
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(
|
||||
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(
|
||||
field('left', $._expression),
|
||||
field('operator', '&'),
|
||||
|
|
|
|||
|
|
@ -17,6 +17,10 @@
|
|||
[
|
||||
"&"
|
||||
"//"
|
||||
"+"
|
||||
"-"
|
||||
"*"
|
||||
"/"
|
||||
"=>"
|
||||
"="
|
||||
">"
|
||||
|
|
|
|||
126
editors/tree-sitter-decodal/src/grammar.json
generated
126
editors/tree-sitter-decodal/src/grammar.json
generated
|
|
@ -127,6 +127,10 @@
|
|||
"type": "SYMBOL",
|
||||
"name": "path_expression"
|
||||
},
|
||||
{
|
||||
"type": "SYMBOL",
|
||||
"name": "unary_expression"
|
||||
},
|
||||
{
|
||||
"type": "SYMBOL",
|
||||
"name": "binary_expression"
|
||||
|
|
@ -844,17 +848,33 @@
|
|||
"type": "FIELD",
|
||||
"name": "value",
|
||||
"content": {
|
||||
"type": "CHOICE",
|
||||
"members": [
|
||||
{
|
||||
"type": "SYMBOL",
|
||||
"name": "integer"
|
||||
},
|
||||
{
|
||||
"type": "SYMBOL",
|
||||
"name": "float"
|
||||
}
|
||||
]
|
||||
"type": "SYMBOL",
|
||||
"name": "_expression"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
"type": "CHOICE",
|
||||
"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",
|
||||
"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",
|
||||
"named": true
|
||||
},
|
||||
{
|
||||
"type": "unary_expression",
|
||||
"named": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -137,6 +141,10 @@
|
|||
{
|
||||
"type": "regex_literal",
|
||||
"named": true
|
||||
},
|
||||
{
|
||||
"type": "unary_expression",
|
||||
"named": true
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
@ -148,6 +156,22 @@
|
|||
"type": "&",
|
||||
"named": false
|
||||
},
|
||||
{
|
||||
"type": "*",
|
||||
"named": false
|
||||
},
|
||||
{
|
||||
"type": "+",
|
||||
"named": false
|
||||
},
|
||||
{
|
||||
"type": "-",
|
||||
"named": false
|
||||
},
|
||||
{
|
||||
"type": "/",
|
||||
"named": false
|
||||
},
|
||||
{
|
||||
"type": "//",
|
||||
"named": false
|
||||
|
|
@ -217,6 +241,10 @@
|
|||
{
|
||||
"type": "regex_literal",
|
||||
"named": true
|
||||
},
|
||||
{
|
||||
"type": "unary_expression",
|
||||
"named": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -294,6 +322,10 @@
|
|||
{
|
||||
"type": "regex_literal",
|
||||
"named": true
|
||||
},
|
||||
{
|
||||
"type": "unary_expression",
|
||||
"named": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -361,6 +393,10 @@
|
|||
{
|
||||
"type": "regex_literal",
|
||||
"named": true
|
||||
},
|
||||
{
|
||||
"type": "unary_expression",
|
||||
"named": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -396,11 +432,67 @@
|
|||
"required": true,
|
||||
"types": [
|
||||
{
|
||||
"type": "float",
|
||||
"type": "array",
|
||||
"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
|
||||
}
|
||||
]
|
||||
|
|
@ -474,6 +566,10 @@
|
|||
{
|
||||
"type": "regex_literal",
|
||||
"named": true
|
||||
},
|
||||
{
|
||||
"type": "unary_expression",
|
||||
"named": true
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
@ -540,6 +636,10 @@
|
|||
{
|
||||
"type": "regex_literal",
|
||||
"named": true
|
||||
},
|
||||
{
|
||||
"type": "unary_expression",
|
||||
"named": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -622,6 +722,10 @@
|
|||
{
|
||||
"type": "regex_literal",
|
||||
"named": true
|
||||
},
|
||||
{
|
||||
"type": "unary_expression",
|
||||
"named": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -709,6 +813,10 @@
|
|||
{
|
||||
"type": "regex_literal",
|
||||
"named": true
|
||||
},
|
||||
{
|
||||
"type": "unary_expression",
|
||||
"named": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -807,6 +915,10 @@
|
|||
{
|
||||
"type": "regex_literal",
|
||||
"named": true
|
||||
},
|
||||
{
|
||||
"type": "unary_expression",
|
||||
"named": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -916,6 +1028,10 @@
|
|||
{
|
||||
"type": "regex_literal",
|
||||
"named": true
|
||||
},
|
||||
{
|
||||
"type": "unary_expression",
|
||||
"named": true
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
@ -986,6 +1102,10 @@
|
|||
{
|
||||
"type": "regex_literal",
|
||||
"named": true
|
||||
},
|
||||
{
|
||||
"type": "unary_expression",
|
||||
"named": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -1058,6 +1178,10 @@
|
|||
{
|
||||
"type": "regex_literal",
|
||||
"named": true
|
||||
},
|
||||
{
|
||||
"type": "unary_expression",
|
||||
"named": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -1155,6 +1279,10 @@
|
|||
{
|
||||
"type": "regex_literal",
|
||||
"named": true
|
||||
},
|
||||
{
|
||||
"type": "unary_expression",
|
||||
"named": true
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
@ -1237,6 +1365,10 @@
|
|||
{
|
||||
"type": "regex_literal",
|
||||
"named": true
|
||||
},
|
||||
{
|
||||
"type": "unary_expression",
|
||||
"named": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -1318,6 +1450,10 @@
|
|||
{
|
||||
"type": "regex_literal",
|
||||
"named": true
|
||||
},
|
||||
{
|
||||
"type": "unary_expression",
|
||||
"named": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -1394,10 +1530,100 @@
|
|||
{
|
||||
"type": "regex_literal",
|
||||
"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": "&",
|
||||
"named": false
|
||||
|
|
@ -1410,14 +1636,30 @@
|
|||
"type": ")",
|
||||
"named": false
|
||||
},
|
||||
{
|
||||
"type": "*",
|
||||
"named": false
|
||||
},
|
||||
{
|
||||
"type": "+",
|
||||
"named": false
|
||||
},
|
||||
{
|
||||
"type": ",",
|
||||
"named": false
|
||||
},
|
||||
{
|
||||
"type": "-",
|
||||
"named": false
|
||||
},
|
||||
{
|
||||
"type": ".",
|
||||
"named": false
|
||||
},
|
||||
{
|
||||
"type": "/",
|
||||
"named": false
|
||||
},
|
||||
{
|
||||
"type": "//",
|
||||
"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: 'Composition', slug: 'language/expression/composition' },
|
||||
{ title: 'Default', slug: 'language/expression/default' },
|
||||
{ title: 'Arithmetic', slug: 'language/expression/arithmetic' },
|
||||
{ title: 'String Interpolation', slug: 'language/expression/string-interpolation' },
|
||||
],
|
||||
},
|
||||
|
|
|
|||
|
|
@ -28,12 +28,13 @@ export function highlightCode(code, language = '') {
|
|||
export function highlightDecodal(source) {
|
||||
let html = '';
|
||||
let index = 0;
|
||||
let canEndExpression = false;
|
||||
|
||||
while (index < source.length) {
|
||||
const char = source[index];
|
||||
const next = source[index + 1];
|
||||
|
||||
if (char === '/' && next === '/') {
|
||||
if (char === '#') {
|
||||
const end = readUntilLineEnd(source, index);
|
||||
html += token('comment', source.slice(index, end));
|
||||
index = end;
|
||||
|
|
@ -44,13 +45,15 @@ export function highlightDecodal(source) {
|
|||
const end = readString(source, index);
|
||||
html += token('string', source.slice(index, end));
|
||||
index = end;
|
||||
canEndExpression = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (char === '/' && next && next !== '/') {
|
||||
if (char === '/' && next && next !== '/' && !canEndExpression) {
|
||||
const end = readRegex(source, index);
|
||||
html += token('regex', source.slice(index, end));
|
||||
index = end;
|
||||
canEndExpression = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -58,6 +61,7 @@ export function highlightDecodal(source) {
|
|||
const end = readNumber(source, index);
|
||||
html += token('number', source.slice(index, end));
|
||||
index = end;
|
||||
canEndExpression = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -68,6 +72,7 @@ export function highlightDecodal(source) {
|
|||
else if (DECODAL_TYPES.has(ident)) html += token('type', ident);
|
||||
else if (DECODAL_LITERALS.has(ident)) html += token('literal', ident);
|
||||
else html += escapeHtml(ident);
|
||||
canEndExpression = true;
|
||||
index = end;
|
||||
continue;
|
||||
}
|
||||
|
|
@ -75,6 +80,7 @@ export function highlightDecodal(source) {
|
|||
if (isOperatorStart(char)) {
|
||||
const end = readOperator(source, index);
|
||||
html += token('operator', source.slice(index, end));
|
||||
canEndExpression = /[})\]]/.test(char);
|
||||
index = end;
|
||||
continue;
|
||||
}
|
||||
|
|
@ -184,11 +190,12 @@ function readIdentifier(source, start) {
|
|||
}
|
||||
|
||||
function isOperatorStart(char) {
|
||||
return /[&=<>!|:;,.{}()[\]]/.test(char ?? '');
|
||||
return '&=<>!|:;,.{}()[]+-*/'.includes(char ?? '');
|
||||
}
|
||||
|
||||
function readOperator(source, start) {
|
||||
let index = start + 1;
|
||||
if (source[start] === '/' && source[index] === '/') return index + 1;
|
||||
if ((source[start] === '<' || source[start] === '>' || source[start] === '=' || source[start] === '!') && source[index] === '=') {
|
||||
index += 1;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ const starterFiles = {
|
|||
in
|
||||
schema.Service & {
|
||||
name = "api";
|
||||
port = 9443;
|
||||
port = 9000 + 443;
|
||||
}
|
||||
`,
|
||||
'schemas/service.dcdl': `Service = {
|
||||
|
|
|
|||
Binary file not shown.
Loading…
Reference in New Issue
Block a user