Add array concat operator
This commit is contained in:
parent
6da0ec4c77
commit
01ad6dca52
|
|
@ -133,6 +133,7 @@ pub enum BinaryOp {
|
||||||
Sub,
|
Sub,
|
||||||
Mul,
|
Mul,
|
||||||
Div,
|
Div,
|
||||||
|
Concat,
|
||||||
Equal,
|
Equal,
|
||||||
NotEqual,
|
NotEqual,
|
||||||
Greater,
|
Greater,
|
||||||
|
|
|
||||||
|
|
@ -410,6 +410,7 @@ impl<L: SourceLoader> Engine<L> {
|
||||||
BinaryOp::Div => {
|
BinaryOp::Div => {
|
||||||
arithmetic(lhs_value, rhs_value, ArithmeticOp::Div, span)
|
arithmetic(lhs_value, rhs_value, ArithmeticOp::Div, span)
|
||||||
}
|
}
|
||||||
|
BinaryOp::Concat => concat_arrays(lhs_value, rhs_value, span),
|
||||||
BinaryOp::Equal => {
|
BinaryOp::Equal => {
|
||||||
compare_expr(lhs_value, rhs_value, CompareExprOp::Equal, span)
|
compare_expr(lhs_value, rhs_value, CompareExprOp::Equal, span)
|
||||||
}
|
}
|
||||||
|
|
@ -1208,6 +1209,23 @@ fn comparison_type_error(span: Span) -> Diagnostic {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn concat_arrays(lhs: RuntimeValue, rhs: RuntimeValue, span: Span) -> Result<RuntimeValue> {
|
||||||
|
match (lhs, rhs) {
|
||||||
|
(
|
||||||
|
RuntimeValue::Concrete(ConcreteValue::Array(mut lhs)),
|
||||||
|
RuntimeValue::Concrete(ConcreteValue::Array(rhs)),
|
||||||
|
) => {
|
||||||
|
lhs.extend(rhs);
|
||||||
|
Ok(RuntimeValue::Concrete(ConcreteValue::Array(lhs)))
|
||||||
|
}
|
||||||
|
_ => Err(Diagnostic::new(
|
||||||
|
DiagnosticKind::TypeMismatch,
|
||||||
|
span,
|
||||||
|
"'++' expects array values",
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn arithmetic(
|
fn arithmetic(
|
||||||
lhs: RuntimeValue,
|
lhs: RuntimeValue,
|
||||||
rhs: RuntimeValue,
|
rhs: RuntimeValue,
|
||||||
|
|
@ -1395,6 +1413,32 @@ mod tests {
|
||||||
assert!(engine.eval_root().is_err());
|
assert!(engine.eval_root().is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn evaluates_array_concat() {
|
||||||
|
let data = eval_data(
|
||||||
|
r#"
|
||||||
|
[1, 2] ++ [3, 4]
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
data,
|
||||||
|
Data::Array(vec![Data::Int(1), Data::Int(2), Data::Int(3), Data::Int(4)])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn array_concat_has_lower_precedence_than_arithmetic() {
|
||||||
|
let data = eval_data("[1 + 1] ++ [2 * 2]");
|
||||||
|
assert_eq!(data, Data::Array(vec![Data::Int(2), Data::Int(4)]));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rejects_invalid_array_concat_operands() {
|
||||||
|
let parsed = parse_source("[1] ++ 2").unwrap();
|
||||||
|
let mut engine = Engine::from_parse(parsed.ast, parsed.root);
|
||||||
|
assert!(engine.eval_root().is_err());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn evaluates_logical_and_comparison_expressions() {
|
fn evaluates_logical_and_comparison_expressions() {
|
||||||
let data = eval_data(
|
let data = eval_data(
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,7 @@ pub enum TokenKind {
|
||||||
AmpAmp,
|
AmpAmp,
|
||||||
PipePipe,
|
PipePipe,
|
||||||
Plus,
|
Plus,
|
||||||
|
PlusPlus,
|
||||||
Minus,
|
Minus,
|
||||||
Star,
|
Star,
|
||||||
Slash,
|
Slash,
|
||||||
|
|
@ -166,7 +167,11 @@ impl<'a> Lexer<'a> {
|
||||||
}
|
}
|
||||||
b'+' => {
|
b'+' => {
|
||||||
self.pos += 1;
|
self.pos += 1;
|
||||||
TokenKind::Plus
|
if self.consume(b'+') {
|
||||||
|
TokenKind::PlusPlus
|
||||||
|
} else {
|
||||||
|
TokenKind::Plus
|
||||||
|
}
|
||||||
}
|
}
|
||||||
b'-' => {
|
b'-' => {
|
||||||
self.pos += 1;
|
self.pos += 1;
|
||||||
|
|
|
||||||
|
|
@ -147,6 +147,14 @@ impl Parser {
|
||||||
},
|
},
|
||||||
span,
|
span,
|
||||||
),
|
),
|
||||||
|
InfixKind::Concat => self.ast.push(
|
||||||
|
Expr::Binary {
|
||||||
|
op: BinaryOp::Concat,
|
||||||
|
lhs,
|
||||||
|
rhs,
|
||||||
|
},
|
||||||
|
span,
|
||||||
|
),
|
||||||
InfixKind::Equal => self.ast.push(
|
InfixKind::Equal => self.ast.push(
|
||||||
Expr::Binary {
|
Expr::Binary {
|
||||||
op: BinaryOp::Equal,
|
op: BinaryOp::Equal,
|
||||||
|
|
@ -532,6 +540,7 @@ impl Parser {
|
||||||
TokenKind::Gte => Some((InfixKind::GreaterEqual, 11, 12)),
|
TokenKind::Gte => Some((InfixKind::GreaterEqual, 11, 12)),
|
||||||
TokenKind::Lt => Some((InfixKind::Less, 11, 12)),
|
TokenKind::Lt => Some((InfixKind::Less, 11, 12)),
|
||||||
TokenKind::Lte => Some((InfixKind::LessEqual, 11, 12)),
|
TokenKind::Lte => Some((InfixKind::LessEqual, 11, 12)),
|
||||||
|
TokenKind::PlusPlus => Some((InfixKind::Concat, 12, 13)),
|
||||||
TokenKind::Plus => Some((InfixKind::Add, 13, 14)),
|
TokenKind::Plus => Some((InfixKind::Add, 13, 14)),
|
||||||
TokenKind::Minus => Some((InfixKind::Sub, 13, 14)),
|
TokenKind::Minus => Some((InfixKind::Sub, 13, 14)),
|
||||||
TokenKind::Star => Some((InfixKind::Mul, 15, 16)),
|
TokenKind::Star => Some((InfixKind::Mul, 15, 16)),
|
||||||
|
|
@ -632,6 +641,7 @@ enum InfixKind {
|
||||||
Sub,
|
Sub,
|
||||||
Mul,
|
Mul,
|
||||||
Div,
|
Div,
|
||||||
|
Concat,
|
||||||
Equal,
|
Equal,
|
||||||
NotEqual,
|
NotEqual,
|
||||||
Greater,
|
Greater,
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,21 @@ array expression は、順序付きの値の列を表す。
|
||||||
["a", "b", "c"]
|
["a", "b", "c"]
|
||||||
```
|
```
|
||||||
|
|
||||||
## 未確定事項
|
## Array concat
|
||||||
|
|
||||||
- 配列要素の制約表現。
|
`++` は concrete array 同士を連結する。
|
||||||
- 異種配列を許可するか。
|
|
||||||
- `//` による patch を右辺置換だけにするか。
|
```dcdl
|
||||||
- append / prepend / remove などの操作を提供するか。
|
base = ["read", "write"];
|
||||||
|
extra = ["admin"];
|
||||||
|
roles = base ++ extra;
|
||||||
|
```
|
||||||
|
|
||||||
|
`roles` は以下と同じ値になる。
|
||||||
|
|
||||||
|
```dcdl
|
||||||
|
["read", "write", "admin"]
|
||||||
|
```
|
||||||
|
|
||||||
|
`++` は配列要素を変換しない。
|
||||||
|
左辺の要素の後に右辺の要素が並ぶ。
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@
|
||||||
| `/` | `lhs / rhs` | arithmetic | concrete `Int` / `Float` | `Float` quotient |
|
| `/` | `lhs / rhs` | arithmetic | concrete `Int` / `Float` | `Float` quotient |
|
||||||
| `+` | `lhs + rhs` | arithmetic | concrete `Int` / `Float` | numeric sum |
|
| `+` | `lhs + rhs` | arithmetic | concrete `Int` / `Float` | numeric sum |
|
||||||
| `-` | `lhs - rhs` | arithmetic | concrete `Int` / `Float` | numeric difference |
|
| `-` | `lhs - rhs` | arithmetic | concrete `Int` / `Float` | numeric difference |
|
||||||
|
| `++` | `lhs ++ rhs` | array concat | concrete arrays | concatenated array |
|
||||||
| `==` | `lhs == rhs` | equality | concrete scalar | concrete `Bool` |
|
| `==` | `lhs == rhs` | equality | concrete scalar | concrete `Bool` |
|
||||||
| `!=` | `lhs != rhs` | equality | concrete scalar | concrete `Bool` |
|
| `!=` | `lhs != rhs` | equality | concrete scalar | concrete `Bool` |
|
||||||
| `<` | `lhs < rhs` | ordering | concrete `Int` / `Float` | concrete `Bool` |
|
| `<` | `lhs < rhs` | ordering | concrete `Int` / `Float` | concrete `Bool` |
|
||||||
|
|
@ -40,12 +41,13 @@
|
||||||
2. unary `!` `-`
|
2. unary `!` `-`
|
||||||
3. `*` `/`
|
3. `*` `/`
|
||||||
4. `+` `-`
|
4. `+` `-`
|
||||||
5. `==` `!=` `<` `<=` `>` `>=`
|
5. `++`
|
||||||
6. `&&`
|
6. `==` `!=` `<` `<=` `>` `>=`
|
||||||
7. `||`
|
7. `&&`
|
||||||
8. `&`
|
8. `||`
|
||||||
9. `//`
|
9. `&`
|
||||||
10. `default`
|
10. `//`
|
||||||
|
11. `default`
|
||||||
|
|
||||||
同じ優先順位の二項演算子は左結合である。
|
同じ優先順位の二項演算子は左結合である。
|
||||||
`default` は右結合である。
|
`default` は右結合である。
|
||||||
|
|
@ -55,6 +57,15 @@
|
||||||
`+` `-` `*` `/` は具体的な `Int` / `Float` に対する四則演算である。
|
`+` `-` `*` `/` は具体的な `Int` / `Float` に対する四則演算である。
|
||||||
詳しくは [Arithmetic Expression](./expression/arithmetic.md) を参照する。
|
詳しくは [Arithmetic Expression](./expression/arithmetic.md) を参照する。
|
||||||
|
|
||||||
|
## Array concat operator
|
||||||
|
|
||||||
|
`++` は concrete array 同士を連結する演算子である。
|
||||||
|
要素は変換されず、左辺の要素の後に右辺の要素が並ぶ。
|
||||||
|
|
||||||
|
```dcdl
|
||||||
|
["read", "write"] ++ ["admin"]
|
||||||
|
```
|
||||||
|
|
||||||
## Logical and comparison operators
|
## Logical and comparison operators
|
||||||
|
|
||||||
`!` `&&` `||` は concrete `Bool` に対する論理演算である。
|
`!` `&&` `||` は concrete `Bool` に対する論理演算である。
|
||||||
|
|
|
||||||
|
|
@ -116,6 +116,7 @@ rec
|
||||||
|
|
||||||
```text
|
```text
|
||||||
+ - * / 四則演算
|
+ - * / 四則演算
|
||||||
|
++ 配列結合
|
||||||
! && || 論理演算
|
! && || 論理演算
|
||||||
== != < <= > >= 比較式
|
== != < <= > >= 比較式
|
||||||
& 制約合成
|
& 制約合成
|
||||||
|
|
|
||||||
|
|
@ -129,3 +129,32 @@ Logical and comparison
|
||||||
left: (unary_expression
|
left: (unary_expression
|
||||||
operand: (identifier))
|
operand: (identifier))
|
||||||
right: (literal (boolean))))))
|
right: (literal (boolean))))))
|
||||||
|
|
||||||
|
==================
|
||||||
|
Array concat
|
||||||
|
==================
|
||||||
|
{
|
||||||
|
roles = ["read"] ++ ["write", "admin"];
|
||||||
|
ports = [8000 + 80] ++ [9443];
|
||||||
|
}
|
||||||
|
---
|
||||||
|
|
||||||
|
(source_file
|
||||||
|
(object
|
||||||
|
(field_definition
|
||||||
|
path: (field_path (identifier))
|
||||||
|
value: (binary_expression
|
||||||
|
left: (array
|
||||||
|
(literal (string)))
|
||||||
|
right: (array
|
||||||
|
(literal (string))
|
||||||
|
(literal (string)))))
|
||||||
|
(field_definition
|
||||||
|
path: (field_path (identifier))
|
||||||
|
value: (binary_expression
|
||||||
|
left: (array
|
||||||
|
(binary_expression
|
||||||
|
left: (literal (integer))
|
||||||
|
right: (literal (integer))))
|
||||||
|
right: (array
|
||||||
|
(literal (integer)))))))
|
||||||
|
|
|
||||||
|
|
@ -5,11 +5,12 @@ const PREC = {
|
||||||
OR: 4,
|
OR: 4,
|
||||||
LOGICAL_AND: 5,
|
LOGICAL_AND: 5,
|
||||||
COMPARE: 6,
|
COMPARE: 6,
|
||||||
ADD: 7,
|
CONCAT: 7,
|
||||||
MUL: 8,
|
ADD: 8,
|
||||||
UNARY: 9,
|
MUL: 9,
|
||||||
CALL: 10,
|
UNARY: 10,
|
||||||
PATH: 11,
|
CALL: 11,
|
||||||
|
PATH: 12,
|
||||||
};
|
};
|
||||||
|
|
||||||
function commaSep(rule) {
|
function commaSep(rule) {
|
||||||
|
|
@ -192,6 +193,11 @@ module.exports = grammar({
|
||||||
field('operator', choice('==', '!=', '>', '>=', '<', '<=')),
|
field('operator', choice('==', '!=', '>', '>=', '<', '<=')),
|
||||||
field('right', $._expression),
|
field('right', $._expression),
|
||||||
)),
|
)),
|
||||||
|
prec.left(PREC.CONCAT, seq(
|
||||||
|
field('left', $._expression),
|
||||||
|
field('operator', token(prec(2, '++'))),
|
||||||
|
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('+', '-')),
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@
|
||||||
"-"
|
"-"
|
||||||
"*"
|
"*"
|
||||||
"/"
|
"/"
|
||||||
|
"++"
|
||||||
"&&"
|
"&&"
|
||||||
"||"
|
"||"
|
||||||
"!"
|
"!"
|
||||||
|
|
|
||||||
50
editors/tree-sitter-decodal/src/grammar.json
generated
50
editors/tree-sitter-decodal/src/grammar.json
generated
|
|
@ -716,7 +716,7 @@
|
||||||
},
|
},
|
||||||
"call_expression": {
|
"call_expression": {
|
||||||
"type": "PREC_LEFT",
|
"type": "PREC_LEFT",
|
||||||
"value": 10,
|
"value": 11,
|
||||||
"content": {
|
"content": {
|
||||||
"type": "SEQ",
|
"type": "SEQ",
|
||||||
"members": [
|
"members": [
|
||||||
|
|
@ -786,7 +786,7 @@
|
||||||
},
|
},
|
||||||
"path_expression": {
|
"path_expression": {
|
||||||
"type": "PREC_LEFT",
|
"type": "PREC_LEFT",
|
||||||
"value": 11,
|
"value": 12,
|
||||||
"content": {
|
"content": {
|
||||||
"type": "SEQ",
|
"type": "SEQ",
|
||||||
"members": [
|
"members": [
|
||||||
|
|
@ -815,7 +815,7 @@
|
||||||
},
|
},
|
||||||
"comparison_constraint": {
|
"comparison_constraint": {
|
||||||
"type": "PREC_RIGHT",
|
"type": "PREC_RIGHT",
|
||||||
"value": 10,
|
"value": 11,
|
||||||
"content": {
|
"content": {
|
||||||
"type": "SEQ",
|
"type": "SEQ",
|
||||||
"members": [
|
"members": [
|
||||||
|
|
@ -857,7 +857,7 @@
|
||||||
},
|
},
|
||||||
"unary_expression": {
|
"unary_expression": {
|
||||||
"type": "PREC",
|
"type": "PREC",
|
||||||
"value": 9,
|
"value": 10,
|
||||||
"content": {
|
"content": {
|
||||||
"type": "SEQ",
|
"type": "SEQ",
|
||||||
"members": [
|
"members": [
|
||||||
|
|
@ -1033,6 +1033,46 @@
|
||||||
{
|
{
|
||||||
"type": "PREC_LEFT",
|
"type": "PREC_LEFT",
|
||||||
"value": 7,
|
"value": 7,
|
||||||
|
"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": 8,
|
||||||
"content": {
|
"content": {
|
||||||
"type": "SEQ",
|
"type": "SEQ",
|
||||||
"members": [
|
"members": [
|
||||||
|
|
@ -1074,7 +1114,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "PREC_LEFT",
|
"type": "PREC_LEFT",
|
||||||
"value": 8,
|
"value": 9,
|
||||||
"content": {
|
"content": {
|
||||||
"type": "SEQ",
|
"type": "SEQ",
|
||||||
"members": [
|
"members": [
|
||||||
|
|
|
||||||
8
editors/tree-sitter-decodal/src/node-types.json
generated
8
editors/tree-sitter-decodal/src/node-types.json
generated
|
|
@ -172,6 +172,10 @@
|
||||||
"type": "+",
|
"type": "+",
|
||||||
"named": false
|
"named": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "++",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "-",
|
"type": "-",
|
||||||
"named": false
|
"named": false
|
||||||
|
|
@ -1692,6 +1696,10 @@
|
||||||
"type": "+",
|
"type": "+",
|
||||||
"named": false
|
"named": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "++",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": ",",
|
"type": ",",
|
||||||
"named": false
|
"named": false
|
||||||
|
|
|
||||||
11282
editors/tree-sitter-decodal/src/parser.c
generated
11282
editors/tree-sitter-decodal/src/parser.c
generated
File diff suppressed because it is too large
Load Diff
6
examples/array-concat.dcdl
Normal file
6
examples/array-concat.dcdl
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
base_roles = ["read", "write"];
|
||||||
|
extra_roles = ["admin"];
|
||||||
|
roles = ["read", "write"] ++ ["admin"];
|
||||||
|
ports = [8000 + 80] ++ [9000 + 443];
|
||||||
|
}
|
||||||
|
|
@ -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[start] === '|' || source[start] === '/') && source[index] === source[start]) return index + 1;
|
if ('&|/+'.includes(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;
|
||||||
|
tags = ["web"] ++ ["prod"];
|
||||||
feature.enable = 9000 + 443 > 9000 && true;
|
feature.enable = 9000 + 443 > 9000 && true;
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
|
|
|
||||||
Binary file not shown.
Loading…
Reference in New Issue
Block a user