fix(tree-sitter): resolve if keyword ambiguity in behavior nodes

Separate if(expr) condition from if(expr) { child } decorator to fix
grammar conflicts. Simplify decorator_params to accept only integer,
range, or duration.
This commit is contained in:
2026-02-13 20:21:31 +00:00
parent b100ded3d3
commit 80332971b8
4 changed files with 6531 additions and 4672 deletions

View File

@@ -130,7 +130,7 @@ module.exports = grammar({
time: $ => /[0-9]{2}:[0-9]{2}(:[0-9]{2})?/, time: $ => /[0-9]{2}:[0-9]{2}(:[0-9]{2})?/,
duration: $ => /[0-9]+[hms]([0-9]+[hms])*/, duration: $ => /[0-9]+[dhms]([0-9]+[dhms])*/,
list: $ => seq('[', commaSep($.value), ']'), list: $ => seq('[', commaSep($.value), ']'),
@@ -232,17 +232,83 @@ module.exports = grammar({
behavior_node: $ => choice( behavior_node: $ => choice(
$.selector_node, $.selector_node,
$.sequence_node, $.sequence_node,
$.repeat_node, $.condition_node,
$.if_decorator_node,
$.decorator_node,
$.action_node, $.action_node,
$.subtree_node $.subtree_node
), ),
selector_node: $ => seq('?', '{', repeat1($.behavior_node), '}'), // Selector node: choose { ... }
selector_node: $ => seq(
'choose',
optional(field('label', $.identifier)),
'{',
repeat1($.behavior_node),
'}'
),
sequence_node: $ => seq('>', '{', repeat1($.behavior_node), '}'), // Sequence node: then { ... }
sequence_node: $ => seq(
'then',
optional(field('label', $.identifier)),
'{',
repeat1($.behavior_node),
'}'
),
repeat_node: $ => seq('*', '{', $.behavior_node, '}'), // Condition node: if(expr) or when(expr) - NO BRACES
condition_node: $ => seq(
choice('if', 'when'),
'(',
field('condition', $.expression),
')'
),
// If decorator: if(expr) { child } - WITH BRACES
if_decorator_node: $ => seq(
'if',
'(',
field('condition', $.expression),
')',
'{',
field('child', $.behavior_node),
'}'
),
// Decorator node: repeat/retry/timeout/etc { child }
decorator_node: $ => seq(
field('decorator', $.decorator_keyword),
optional(field('params', $.decorator_params)),
'{',
field('child', $.behavior_node),
'}'
),
decorator_keyword: $ => choice(
'repeat',
'invert',
'retry',
'timeout',
'cooldown',
'succeed_always',
'fail_always'
),
decorator_params: $ => seq(
'(',
choice(
// min..max range (for repeat)
seq($.integer, '..', $.integer),
// N (for repeat, retry)
$.integer,
// duration (for timeout, cooldown)
$.duration
),
')'
),
// Action node: action_name or action_name(params)
action_node: $ => choice( action_node: $ => choice(
seq($.identifier, '(', commaSep($.action_param), ')'), seq($.identifier, '(', commaSep($.action_param), ')'),
$.identifier $.identifier
@@ -255,7 +321,8 @@ module.exports = grammar({
$.value $.value
), ),
subtree_node: $ => seq('@', $.path), // Subtree node: include path::to::subtree
subtree_node: $ => seq('include', $.path),
// Institution declaration // Institution declaration
institution: $ => seq( institution: $ => seq(
@@ -275,12 +342,10 @@ module.exports = grammar({
), ),
participant: $ => choice( participant: $ => choice(
// name { fields } // name as role { fields } (role optional)
seq($.path, $.block),
// name as role { fields }
seq($.path, 'as', $.identifier, $.block), seq($.path, 'as', $.identifier, $.block),
// bare name // name { fields } (block required)
$.path seq($.path, $.block)
), ),
// Location declaration // Location declaration

View File

@@ -581,7 +581,7 @@
}, },
"duration": { "duration": {
"type": "PATTERN", "type": "PATTERN",
"value": "[0-9]+[hms]([0-9]+[hms])*" "value": "[0-9]+[dhms]([0-9]+[dhms])*"
}, },
"list": { "list": {
"type": "SEQ", "type": "SEQ",
@@ -1077,7 +1077,15 @@
}, },
{ {
"type": "SYMBOL", "type": "SYMBOL",
"name": "repeat_node" "name": "condition_node"
},
{
"type": "SYMBOL",
"name": "if_decorator_node"
},
{
"type": "SYMBOL",
"name": "decorator_node"
}, },
{ {
"type": "SYMBOL", "type": "SYMBOL",
@@ -1094,7 +1102,23 @@
"members": [ "members": [
{ {
"type": "STRING", "type": "STRING",
"value": "?" "value": "choose"
},
{
"type": "CHOICE",
"members": [
{
"type": "FIELD",
"name": "label",
"content": {
"type": "SYMBOL",
"name": "identifier"
}
},
{
"type": "BLANK"
}
]
}, },
{ {
"type": "STRING", "type": "STRING",
@@ -1118,7 +1142,23 @@
"members": [ "members": [
{ {
"type": "STRING", "type": "STRING",
"value": ">" "value": "then"
},
{
"type": "CHOICE",
"members": [
{
"type": "FIELD",
"name": "label",
"content": {
"type": "SYMBOL",
"name": "identifier"
}
},
{
"type": "BLANK"
}
]
}, },
{ {
"type": "STRING", "type": "STRING",
@@ -1137,20 +1177,74 @@
} }
] ]
}, },
"repeat_node": { "condition_node": {
"type": "SEQ",
"members": [
{
"type": "CHOICE",
"members": [
{
"type": "STRING",
"value": "if"
},
{
"type": "STRING",
"value": "when"
}
]
},
{
"type": "STRING",
"value": "("
},
{
"type": "FIELD",
"name": "condition",
"content": {
"type": "SYMBOL",
"name": "expression"
}
},
{
"type": "STRING",
"value": ")"
}
]
},
"if_decorator_node": {
"type": "SEQ", "type": "SEQ",
"members": [ "members": [
{ {
"type": "STRING", "type": "STRING",
"value": "*" "value": "if"
},
{
"type": "STRING",
"value": "("
},
{
"type": "FIELD",
"name": "condition",
"content": {
"type": "SYMBOL",
"name": "expression"
}
},
{
"type": "STRING",
"value": ")"
}, },
{ {
"type": "STRING", "type": "STRING",
"value": "{" "value": "{"
}, },
{ {
"type": "SYMBOL", "type": "FIELD",
"name": "behavior_node" "name": "child",
"content": {
"type": "SYMBOL",
"name": "behavior_node"
}
}, },
{ {
"type": "STRING", "type": "STRING",
@@ -1158,6 +1252,127 @@
} }
] ]
}, },
"decorator_node": {
"type": "SEQ",
"members": [
{
"type": "FIELD",
"name": "decorator",
"content": {
"type": "SYMBOL",
"name": "decorator_keyword"
}
},
{
"type": "CHOICE",
"members": [
{
"type": "FIELD",
"name": "params",
"content": {
"type": "SYMBOL",
"name": "decorator_params"
}
},
{
"type": "BLANK"
}
]
},
{
"type": "STRING",
"value": "{"
},
{
"type": "FIELD",
"name": "child",
"content": {
"type": "SYMBOL",
"name": "behavior_node"
}
},
{
"type": "STRING",
"value": "}"
}
]
},
"decorator_keyword": {
"type": "CHOICE",
"members": [
{
"type": "STRING",
"value": "repeat"
},
{
"type": "STRING",
"value": "invert"
},
{
"type": "STRING",
"value": "retry"
},
{
"type": "STRING",
"value": "timeout"
},
{
"type": "STRING",
"value": "cooldown"
},
{
"type": "STRING",
"value": "succeed_always"
},
{
"type": "STRING",
"value": "fail_always"
}
]
},
"decorator_params": {
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "("
},
{
"type": "CHOICE",
"members": [
{
"type": "SEQ",
"members": [
{
"type": "SYMBOL",
"name": "integer"
},
{
"type": "STRING",
"value": ".."
},
{
"type": "SYMBOL",
"name": "integer"
}
]
},
{
"type": "SYMBOL",
"name": "integer"
},
{
"type": "SYMBOL",
"name": "duration"
}
]
},
{
"type": "STRING",
"value": ")"
}
]
},
"action_node": { "action_node": {
"type": "CHOICE", "type": "CHOICE",
"members": [ "members": [
@@ -1260,7 +1475,7 @@
"members": [ "members": [
{ {
"type": "STRING", "type": "STRING",
"value": "@" "value": "include"
}, },
{ {
"type": "SYMBOL", "type": "SYMBOL",
@@ -1331,19 +1546,6 @@
"participant": { "participant": {
"type": "CHOICE", "type": "CHOICE",
"members": [ "members": [
{
"type": "SEQ",
"members": [
{
"type": "SYMBOL",
"name": "path"
},
{
"type": "SYMBOL",
"name": "block"
}
]
},
{ {
"type": "SEQ", "type": "SEQ",
"members": [ "members": [
@@ -1366,8 +1568,17 @@
] ]
}, },
{ {
"type": "SYMBOL", "type": "SEQ",
"name": "path" "members": [
{
"type": "SYMBOL",
"name": "path"
},
{
"type": "SYMBOL",
"name": "block"
}
]
} }
] ]
}, },

View File

@@ -135,7 +135,15 @@
"named": true "named": true
}, },
{ {
"type": "repeat_node", "type": "condition_node",
"named": true
},
{
"type": "decorator_node",
"named": true
},
{
"type": "if_decorator_node",
"named": true "named": true
}, },
{ {
@@ -261,6 +269,22 @@
] ]
} }
}, },
{
"type": "condition_node",
"named": true,
"fields": {
"condition": {
"multiple": false,
"required": true,
"types": [
{
"type": "expression",
"named": true
}
]
}
}
},
{ {
"type": "declaration", "type": "declaration",
"named": true, "named": true,
@@ -316,6 +340,66 @@
] ]
} }
}, },
{
"type": "decorator_keyword",
"named": true,
"fields": {}
},
{
"type": "decorator_node",
"named": true,
"fields": {
"child": {
"multiple": false,
"required": true,
"types": [
{
"type": "behavior_node",
"named": true
}
]
},
"decorator": {
"multiple": false,
"required": true,
"types": [
{
"type": "decorator_keyword",
"named": true
}
]
},
"params": {
"multiple": false,
"required": false,
"types": [
{
"type": "decorator_params",
"named": true
}
]
}
}
},
{
"type": "decorator_params",
"named": true,
"fields": {},
"children": {
"multiple": true,
"required": true,
"types": [
{
"type": "duration",
"named": true
},
{
"type": "integer",
"named": true
}
]
}
},
{ {
"type": "dotted_path", "type": "dotted_path",
"named": true, "named": true,
@@ -447,6 +531,32 @@
] ]
} }
}, },
{
"type": "if_decorator_node",
"named": true,
"fields": {
"child": {
"multiple": false,
"required": true,
"types": [
{
"type": "behavior_node",
"named": true
}
]
},
"condition": {
"multiple": false,
"required": true,
"types": [
{
"type": "expression",
"named": true
}
]
}
}
},
{ {
"type": "include", "type": "include",
"named": true, "named": true,
@@ -836,21 +946,6 @@
] ]
} }
}, },
{
"type": "repeat_node",
"named": true,
"fields": {},
"children": {
"multiple": false,
"required": true,
"types": [
{
"type": "behavior_node",
"named": true
}
]
}
},
{ {
"type": "schedule", "type": "schedule",
"named": true, "named": true,
@@ -930,7 +1025,18 @@
{ {
"type": "selector_node", "type": "selector_node",
"named": true, "named": true,
"fields": {}, "fields": {
"label": {
"multiple": false,
"required": false,
"types": [
{
"type": "identifier",
"named": true
}
]
}
},
"children": { "children": {
"multiple": true, "multiple": true,
"required": true, "required": true,
@@ -945,7 +1051,18 @@
{ {
"type": "sequence_node", "type": "sequence_node",
"named": true, "named": true,
"fields": {}, "fields": {
"label": {
"multiple": false,
"required": false,
"types": [
{
"type": "identifier",
"named": true
}
]
}
},
"children": { "children": {
"multiple": true, "multiple": true,
"required": true, "required": true,
@@ -1222,10 +1339,6 @@
"type": ">=", "type": ">=",
"named": false "named": false
}, },
{
"type": "?",
"named": false
},
{ {
"type": "@", "type": "@",
"named": false "named": false
@@ -1262,6 +1375,14 @@
"type": "character", "type": "character",
"named": false "named": false
}, },
{
"type": "choose",
"named": false
},
{
"type": "cooldown",
"named": false
},
{ {
"type": "duration", "type": "duration",
"named": true "named": true
@@ -1274,6 +1395,10 @@
"type": "enum", "type": "enum",
"named": false "named": false
}, },
{
"type": "fail_always",
"named": false
},
{ {
"type": "false", "type": "false",
"named": false "named": false
@@ -1290,6 +1415,10 @@
"type": "identifier", "type": "identifier",
"named": true "named": true
}, },
{
"type": "if",
"named": false
},
{ {
"type": "include", "type": "include",
"named": false "named": false
@@ -1302,6 +1431,10 @@
"type": "integer", "type": "integer",
"named": true "named": true
}, },
{
"type": "invert",
"named": false
},
{ {
"type": "is", "type": "is",
"named": false "named": false
@@ -1350,6 +1483,14 @@
"type": "remove", "type": "remove",
"named": false "named": false
}, },
{
"type": "repeat",
"named": false
},
{
"type": "retry",
"named": false
},
{ {
"type": "schedule", "type": "schedule",
"named": false "named": false
@@ -1374,14 +1515,26 @@
"type": "string", "type": "string",
"named": true "named": true
}, },
{
"type": "succeed_always",
"named": false
},
{ {
"type": "template", "type": "template",
"named": false "named": false
}, },
{
"type": "then",
"named": false
},
{ {
"type": "time", "type": "time",
"named": true "named": true
}, },
{
"type": "timeout",
"named": false
},
{ {
"type": "true", "type": "true",
"named": false "named": false
@@ -1390,6 +1543,10 @@
"type": "use", "type": "use",
"named": false "named": false
}, },
{
"type": "when",
"named": false
},
{ {
"type": "{", "type": "{",
"named": false "named": false

File diff suppressed because it is too large Load Diff