add script for extracting EBNF grammar from doccomments

This commit is contained in:
Moritz Hölting 2025-08-13 09:37:18 +02:00
parent 6e27474da4
commit 3271ca514c
9 changed files with 619 additions and 160 deletions

View File

@ -1,63 +1,145 @@
# Grammar of the Shulkerscript language
## Table of contents
## Program
### Program
```ebnf
Program: Namespace Declaration*;
Program:
Namespace
Declaration*
;
```
### Namespace
## Declaration
```ebnf
Namespace: 'namespace' StringLiteral;
Declaration:
Function
| Import
| TagDeclaration
| ('pub'? VariableDeclaration ';')
;
```
### StringLiteral
## Namespace
```ebnf
StringLiteral: '"' TEXT '"';
Namespace:
'namespace' StringLiteral ';' ;
```
### MacroStringLiteral
```ebnf
MacroStringLiteral: '`' ( TEXT | '$(' [a-zA-Z0-9_]+ ')' )* '`';
```
## Function
### AnyStringLiteral
```ebnf
AnyStringLiteral: StringLiteral | MacroStringLiteral;
```
### Declaration
```ebnf
Declaration: FunctionDeclaration | Import | TagDeclaration;
```
### Import
```ebnf
Import: 'from' StringLiteral 'import' Identifier;
```
### TagDeclaration
```ebnf
TagDeclaration: 'tag' StringLiteral ('of' StringLiteral)? 'replace'? '[' (StringLiteral (',' StringLiteral)*)? ']';
```
### FunctionDeclaration
```ebnf
Function:
Annotation* 'pub'? 'fn' Identifier '(' ParameterList? ')' Block
;
ParameterList:
Identifier (',' Identifier)* ','?
Annotation* 'pub'? 'fn' Identifier '(' FunctionParameterList? ')' Block
;
```
### Annotation
## Import
```ebnf
Annotation: '#[' Identifier ('=' StringLiteral)? ']';
Import:
'from' StringLiteral 'import' ('*' | Identifier (',' Identifier)*) ';'
;
```
### Statement
## TagDeclaration
```ebnf
TagDeclaration:
'tag' ('<' StringLiteral '>')? StringLiteral 'replace'? '[' (StringLiteral (',' StringLiteral)*)? ']'
;
```
## VariableDeclaration
```ebnf
VariableDeclaration:
SingleVariableDeclaration
| ArrayVariableDeclaration
| ScoreVariableDeclaration
| TagVariableDeclaration
| ComptimeValueDeclaration
;
```
## StringLiteral
```ebnf
StringLiteral:
'"' TEXT '"';
```
## Annotation
```ebnf
Annotation:
'#[' AnnotationAssignment ']'
;
```
## Block
```ebnf
Block:
'{' Statement* '}'
;
```
## FunctionParameterList
```ebnf
FunctionParameterList:
FunctionArgument (',' FunctionArgument)* ','?
;
```
## ArrayVariableDeclaration
```ebnf
ArrayVariableDeclaration:
('int' | 'bool') identifier '[' integer ']' VariableDeclarationAssignment?
```
## ComptimeValueDeclaration
```ebnf
ComptimeValueDeclaration:
'val' identifier VariableDeclarationAssignment?
```
## ScoreVariableDeclaration
```ebnf
ScoreVariableDeclaration:
'int' ('<' StringLiteral '>')? identifier '[' AnyStringLiteral? ']' VariableDeclarationAssignment?
```
## SingleVariableDeclaration
```ebnf
SingleVariableDeclaration:
('int' | 'bool') identifier VariableDeclarationAssignment?
```
## TagVariableDeclaration
```ebnf
TagVariableDeclaration:
'bool' identifier '[' AnyStringLiteral? ']' VariableDeclarationAssignment?
```
## AnnotationAssignment
```ebnf
AnnotationAssignment:
Identifier AnnotationValue
;
```
## Statement
```ebnf
Statement:
Block
@ -65,115 +147,345 @@ Statement:
| Conditional
| Grouping
| DocComment
| ExecuteBlock
| Semicolon
| Run
;
```
### Block
```ebnf
Block: '{' Statement* '}';
```
## FunctionArgument
### Run
```ebnf
Run:
'run' Expression ';'
FunctionArgument:
FunctionVariableType Identifier
;
```
### Conditional
## VariableDeclarationAssignment
```ebnf
VariableDeclarationAssignment:
'=' Expression
```
## AnyStringLiteral
```ebnf
AnyStringLiteral: StringLiteral | MacroStringLiteral ;
```
## AnnotationValue
```ebnf
AnnotationValue:
'=' Expression
| '(' AnnotationAssignment ( ',' AnnotationAssignment )* ')'
;
```
## Conditional
```ebnf
Conditional:
'if' ParenthizedCondition Block ('else' Block)?
;
'if' Parenthized
;
```
### Condition
## ExecuteBlock
```ebnf
Condition:
PrimaryCondition
BinaryCondition
;
ExecuteBlock:
(ExecuteBlockHead ExecuteBlockTail)
| (Conditional Block Else)
;
```
#### PrimaryCondition
## Grouping
```ebnf
PrimaryCondition:
ConditionalPrefix
| ParenthesizedCondition
| AnyStringLiteral
;
Grouping:
'group' Block
;
```
#### ConditionalPrefix
## Semicolon
```ebnf
ConditionalPrefix:
ConditionalPrefixOperator PrimaryCondition
Semicolon:
SemicolonStatement ';'
;
```
## FunctionVariableType
```ebnf
FunctionVariableType:
'macro' | 'int' | 'bool'
;
```
#### ConditionalPrefixOperator
``` ebnf
ConditionalPrefixOperator: '!';
## Expression
```ebnf
Expression:
Primary | Binary ;
```
#### BinaryCondition
``` ebnf
BinaryCondition:
Condition ConditionalBinaryOperator Condition
## MacroStringLiteral
```ebnf
MacroStringLiteral:
'`' ( TEXT | '$(' [a-zA-Z0-9_]+ ')' )* '`';
```
## Else
```ebnf
Else:
'else' Block
;
```
#### ConditionalBinaryOperator
``` ebnf
ConditionalBinaryOperator:
'&&'
## ExecuteBlockHead
```ebnf
ExecuteBlockHead:
Conditional
| Align
| Anchored
| As
| AsAt
| At
| Facing
| In
| On
| Positioned
| Rotated
| Store
| Summon
;
```
## ExecuteBlockTail
```ebnf
ExecuteBlockTail:
ExecuteBlock
| Block
;
```
## SemicolonStatement
```ebnf
SemicolonStatement:
(Expression | VariableDeclaration | Assignment | ReturnStatement)
';'
;
```
## Binary
```ebnf
Binary:
Expression BinaryOperator Expression
;
```
## Primary
```ebnf
Primary:
Identifier
| Prefix
| Parenthesized
| Indexed
| Integer
| Boolean
| StringLiteral
| FunctionCall
| MemberAccess
| MacroStringLiteral
| LuaCode
```
## Align
```ebnf
Align:
'align' '(' AnyStringLiteral ')' ;
```
## Anchored
```ebnf
Anchored:
'anchored' '(' AnyStringLiteral ')' ;
```
## As
```ebnf
As:
'as' '(' AnyStringLiteral ')' ;
```
## AsAt
```ebnf
AsAt:
'asat' '(' AnyStringLiteral ')' ;
```
## At
```ebnf
At:
'at' '(' AnyStringLiteral ')' ;
```
## Facing
```ebnf
Facing:
'facing' '(' AnyStringLiteral ')' ;
```
## In
```ebnf
In:
'in' '(' AnyStringLiteral ')' ;
```
## On
```ebnf
On:
'on' '(' AnyStringLiteral ')' ;
```
## Positioned
```ebnf
Positioned:
'positioned' '(' AnyStringLiteral ')' ;
```
## Rotated
```ebnf
Rotated:
'rotated' '(' AnyStringLiteral ')' ;
```
## Store
```ebnf
Store:
'store' '(' AnyStringLiteral ')' ;
```
## Summon
```ebnf
Summon:
'summon' '(' AnyStringLiteral ')' ;
```
## Assignment
```ebnf
Assignment:
AssignmentDestination '=' Expression
```
## ReturnStatement
```ebnf
ReturnStatement:
`return` Expression ;
```
## BinaryOperator
```ebnf
BinaryOperator:
'+'
| '-'
| '*'
| '/'
| '%'
| '=='
| '!='
| '<'
| '<='
| '>'
| '>='
| '&&'
| '||'
;
```
#### ParenthizedCondition
```ebnf
ParenthizedCondition:
'(' Condition ')'
;
```
## FunctionCall
### Grouping
``` ebnf
Grouping:
'group' Block
;
```
### Expression
```ebnf
Expression:
Primary
;
```
### Primary
```ebnf
Primary:
FunctionCall
| AnyStringLiteral
| LuaCode
;
```
### FunctionCall
```ebnf
FunctionCall:
Identifier '(' (Expression (',' Expression)*)? ')'
;
```
### LuaCode
## Indexed
```ebnf
Indexed:
PrimaryExpression '[' Expression ']'
;
```
## LuaCode
```ebnf
LuaCode:
'lua' '(' (Expression (',' Expression)*)? ')' '{' (.*?)* '}'
```
## MemberAccess
```ebnf
MemberAccess:
Primary '.' Identifier
```
## Parenthesized
```ebnf
Parenthesized:
'(' Expression ')'
;
```
## Prefix
```ebnf
Prefix:
PrefixOperator Primary
;
```
## AssignmentDestination
```ebnf
AssignmentDestination:
Identifier
| Identifier '[' Expression ']'
;
```
## PrefixOperator
```ebnf
PrefixOperator:
'!' | '-' | 'run'
;
```

View File

@ -0,0 +1,136 @@
#!/usr/bin/env python3
import re
import os
from pathlib import Path
from collections import defaultdict, deque
ebnf_blocks = []
rule_defs = {}
rule_deps = defaultdict(set)
ebnf_fence_start = re.compile(r"^\s*///\s*```\s*ebnf\s*$")
ebnf_fence_end = re.compile(r"^\s*///\s*```\s*$")
doc_comment_prefix = re.compile(r"^\s*///\s?(.*)$")
rule_start_pattern = re.compile(r"^\s*([A-Za-z_]\w*)\s*:")
rule_ref_pattern = re.compile(r"\b([A-Za-z_]\w*)\b")
def find_project_root() -> Path | None:
current = Path.cwd()
while current != current.parent:
cargo_toml = current / "Cargo.toml"
if cargo_toml.exists():
text = cargo_toml.read_text(encoding="utf-8")
if re.search(r'(?m)^\s*name\s*=\s*"shulkerscript"\s*$', text):
return current
current = current.parent
return None
root_dir = find_project_root()
if not root_dir:
raise SystemExit(
"Could not find Cargo.toml of package 'shulkerscript' in this or any parent directory."
)
if Path.cwd() != root_dir:
os.chdir(root_dir)
print(f"Changed working directory to {root_dir}")
previous_rules = set()
with open("grammar.md", "r", encoding="utf-8") as f:
rule_header_pattern = re.compile(r"## (\w+)")
for line in f:
m = rule_header_pattern.match(line)
if m:
previous_rules.add(m.group(1))
for path in Path(".").rglob("*.rs"):
with path.open(encoding="utf-8") as f:
in_block = False
current_block_lines = []
for line in f:
if not in_block and ebnf_fence_start.match(line):
in_block = True
current_block_lines = []
continue
if in_block:
if ebnf_fence_end.match(line):
block_text = "\n".join(current_block_lines)
ebnf_blocks.append(block_text)
current_rule_name = None
current_rule_lines = []
for ln in current_block_lines:
m = rule_start_pattern.match(ln)
if m:
if current_rule_name:
full_def = "\n".join(current_rule_lines)
rule_defs[current_rule_name] = full_def
refs = set(rule_ref_pattern.findall(full_def))
refs.discard(current_rule_name)
rule_deps[current_rule_name].update(refs)
current_rule_name = m.group(1)
current_rule_lines = [ln]
else:
if current_rule_name:
current_rule_lines.append(ln)
if current_rule_name:
full_def = "\n".join(current_rule_lines)
rule_defs[current_rule_name] = full_def
refs = set(rule_ref_pattern.findall(full_def))
refs.discard(current_rule_name)
rule_deps[current_rule_name].update(refs)
in_block = False
continue
m = doc_comment_prefix.match(line)
if m:
current_block_lines.append(m.group(1))
if "Program" not in rule_defs:
raise SystemExit("Root rule 'Program' not found in EBNF definitions")
visited = set()
order = []
queue = deque(["Program"])
while queue:
rule = queue.popleft()
if rule not in visited and rule in rule_defs:
visited.add(rule)
order.append(rule)
for dep in sorted(rule_deps[rule]):
if dep not in visited:
queue.append(dep)
unused_rules = sorted(set(rule_defs.keys()) - visited)
if len(unused_rules) > 0:
print(
f"Appending {len(unused_rules)} unused rules to the end: {', '.join(unused_rules)}"
)
order.extend(unused_rules)
with open("grammar.md", "w", encoding="utf-8") as out:
out.write("# Grammar of the Shulkerscript language\n\n")
for rule in order:
out.write(f"## {rule}\n\n```ebnf\n{rule_defs[rule]}\n```\n\n")
print(f"Wrote grammar.md with {len(order)} rules.")
added_rules = set(rule_defs.keys()) - previous_rules
if len(added_rules) > 0:
print(f"Added rules for: {', '.join(added_rules)}")
removed_rules = previous_rules - set(rule_defs.keys())
if len(removed_rules) > 0:
print(f"Removed rules for: {', '.join(removed_rules)}")

View File

@ -310,6 +310,11 @@ impl Debug for Boolean {
}
/// Represents a hardcoded string literal value in the source code.
///
/// ```ebnf
/// StringLiteral:
/// '"' TEXT '"';
/// ```
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct StringLiteral {
@ -344,6 +349,11 @@ impl SourceElement for StringLiteral {
}
/// Represents a hardcoded macro string literal value in the source code.
///
/// ```ebnf
/// MacroStringLiteral:
/// '`' ( TEXT | '$(' [a-zA-Z0-9_]+ ')' )* '`';
/// ```
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct MacroStringLiteral {

View File

@ -32,11 +32,11 @@ use super::{
///
/// Syntax Synopsis:
///
/// ``` ebnf
/// ```ebnf
/// Declaration:
/// Function
/// | Import
/// | Tag
/// | TagDeclaration
/// | ('pub'? VariableDeclaration ';')
/// ;
/// ```
@ -97,12 +97,12 @@ impl Declaration {
///
/// Syntax Synopsis:
///
/// ``` ebnf
/// ```ebnf
/// Function:
/// Annotation* 'pub'? 'fn' Identifier '(' ParameterList? ')' Block
/// Annotation* 'pub'? 'fn' Identifier '(' FunctionParameterList? ')' Block
/// ;
///
/// ParameterList:
/// FunctionParameterList:
/// FunctionArgument (',' FunctionArgument)* ','?
/// ;
/// ```
@ -176,7 +176,7 @@ impl SourceElement for Function {
///
/// Syntax Synopsis:
///
/// ``` ebnf
/// ```ebnf
/// FunctionVariableType:
/// 'macro' | 'int' | 'bool'
/// ;
@ -193,7 +193,7 @@ pub enum FunctionVariableType {
///
/// Syntax Synopsis:
///
/// ``` ebnf
/// ```ebnf
/// FunctionArgument:
/// FunctionVariableType Identifier
/// ;
@ -211,7 +211,7 @@ pub struct FunctionParameter {
///
/// Syntax Synopsis:
///
/// ``` ebnf
/// ```ebnf
/// Import:
/// 'from' StringLiteral 'import' ('*' | Identifier (',' Identifier)*) ';'
/// ;
@ -266,7 +266,7 @@ impl SourceElement for Import {
///
/// Syntax Synopsis:
///
/// ``` ebnf
/// ```ebnf
/// TagDeclaration:
/// 'tag' ('<' StringLiteral '>')? StringLiteral 'replace'? '[' (StringLiteral (',' StringLiteral)*)? ']'
/// ;

View File

@ -143,7 +143,7 @@ impl SourceElement for Binary {
///
/// ```ebnf
/// Expression:
/// Primary | Binary
/// Primary | Binary ;
/// ```
#[allow(missing_docs)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
@ -166,11 +166,12 @@ impl SourceElement for Expression {
///
/// Syntax Synopsis:
///
/// ``` ebnf
/// ```ebnf
/// Primary:
/// Identifier
/// | Prefix
/// | Parenthesized
/// | Indexed
/// | Integer
/// | Boolean
/// | StringLiteral
@ -307,7 +308,7 @@ impl SourceElement for Indexed {
/// Syntax Synopsis:
/// ```ebnf
/// PrefixOperator:
/// '!' | '-'
/// '!' | '-' | 'run'
/// ;
/// ```
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
@ -359,7 +360,7 @@ impl SourceElement for Prefix {
///
/// Syntax Synopsis:
///
/// ``` ebnf
/// ```ebnf
/// FunctionCall:
/// Identifier '(' (Expression (',' Expression)*)? ')'
/// ;

View File

@ -96,7 +96,7 @@ impl SourceElement for AnyStringLiteral {
///
/// Syntax Synopsis:
///
/// ``` ebnf
/// ```ebnf
/// Annotation:
/// '#[' AnnotationAssignment ']'
/// ;
@ -149,7 +149,7 @@ impl SourceElement for Annotation {
///
/// Syntax Synopsis:
///
/// ``` ebnf
/// ```ebnf
/// AnnotationValue:
/// '=' Expression
/// | '(' AnnotationAssignment ( ',' AnnotationAssignment )* ')'
@ -200,7 +200,7 @@ impl SourceElement for AnnotationValue {
///
/// Syntax Synopsis:
///
/// ``` ebnf
/// ```ebnf
/// AnnotationAssignment:
/// Identifier AnnotationValue
/// ;

View File

@ -20,6 +20,13 @@ use crate::{
use super::declaration::Declaration;
/// Program is a collection of declarations preceeded by a namespace selector.
///
/// ```ebnf
/// Program:
/// Namespace
/// Declaration*
/// ;
/// ```
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Getters)]
pub struct ProgramFile {

View File

@ -35,13 +35,14 @@ use super::{expression::Expression, Annotation, AnyStringLiteral};
///
/// Syntax Synopsis:
///
/// ``` ebnf
/// ```ebnf
/// Statement:
/// Block
/// | LiteralCommand
/// | Conditional
/// | Grouping
/// | DocComment
/// | ExecuteBlock
/// | Semicolon
/// ;
/// ```
@ -131,7 +132,7 @@ impl Statement {
///
/// Syntax Synopsis:
///
/// ``` ebnf
/// ```ebnf
/// Block:
/// '{' Statement* '}'
/// ;
@ -171,11 +172,11 @@ impl SourceElement for Block {
///
/// Syntax Synopsis:
///
/// ``` ebnf
/// ```ebnf
/// Grouping:
/// 'group' Block
/// 'group' Block
/// ;
/// ````
/// ```
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Getters)]
pub struct Grouping {
@ -207,9 +208,9 @@ impl SourceElement for Grouping {
/// Represents a statement that ends with a semicolon in the syntax tree.
///
/// Syntax Synopsis:
/// ``` ebnf
/// ```ebnf
/// Semicolon:
/// SemicolonStatement ';'
/// SemicolonStatement ';'
/// ;
/// ```
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
@ -243,10 +244,10 @@ impl Semicolon {
/// Represents a statement that ends with a semicolon in the syntax tree.
///
/// Syntax Synopsis:
/// ``` ebnf
/// ```ebnf
/// SemicolonStatement:
/// (Expression | VariableDeclaration | Assignment)
/// ';'
/// (Expression | VariableDeclaration | Assignment | ReturnStatement)
/// ';'
/// ;
/// ```
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
@ -318,6 +319,7 @@ impl ReturnStatement {
/// | ArrayVariableDeclaration
/// | ScoreVariableDeclaration
/// | TagVariableDeclaration
/// | ComptimeValueDeclaration
/// ;
/// ```
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]

View File

@ -73,6 +73,7 @@ impl SourceElement for ExecuteBlock {
/// | Store
/// | Summon
/// ;
/// ```
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, EnumAsInner, From)]
#[allow(missing_docs)]
@ -120,6 +121,7 @@ impl SourceElement for ExecuteBlockHead {
/// ExecuteBlock
/// | Block
/// ;
/// ```
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, EnumAsInner, From)]
#[allow(missing_docs)]
@ -144,7 +146,7 @@ impl SourceElement for ExecuteBlockTail {
///
/// Syntax Synopsis:
///
/// ``` ebnf
/// ```ebnf
/// Conditional:
/// 'if' Parenthized
/// ;
@ -181,7 +183,7 @@ impl SourceElement for Conditional {
///
/// Syntax Synopsis:
///
/// ``` ebnf
/// ```ebnf
/// Else:
/// 'else' Block
/// ;
@ -217,8 +219,7 @@ impl SourceElement for Else {
///
/// ```ebnf
/// As:
/// 'as' '(' AnyStringLiteral ')'
/// ;
/// 'as' '(' AnyStringLiteral ')' ;
/// ```
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Getters)]
@ -263,8 +264,8 @@ impl As {
/// Syntax Synopsis:
/// ```ebnf
/// Align:
/// 'align' '(' AnyStringLiteral ')'
/// ;
/// 'align' '(' AnyStringLiteral ')' ;
/// ```
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Getters)]
pub struct Align {
@ -309,8 +310,7 @@ impl Align {
/// Syntax Synopsis:
/// ```ebnf
/// Anchored:
/// 'anchored' '(' AnyStringLiteral ')'
/// ;
/// 'anchored' '(' AnyStringLiteral ')' ;
/// ```
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Getters)]
@ -354,8 +354,7 @@ impl Anchored {
/// Syntax Synopsis:
/// ```ebnf
/// AsAt:
/// 'asat' '(' AnyStringLiteral ')'
/// ;
/// 'asat' '(' AnyStringLiteral ')' ;
/// ```
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Getters)]
@ -399,8 +398,7 @@ impl AsAt {
/// Syntax Synopsis:
/// ```ebnf
/// At:
/// 'at' '(' AnyStringLiteral ')'
/// ;
/// 'at' '(' AnyStringLiteral ')' ;
/// ```
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Getters)]
@ -444,8 +442,7 @@ impl At {
/// Syntax Synopsis:
/// ```ebnf
/// Facing:
/// 'facing' '(' AnyStringLiteral ')'
/// ;
/// 'facing' '(' AnyStringLiteral ')' ;
/// ```
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Getters)]
@ -489,8 +486,7 @@ impl Facing {
/// Syntax Synopsis:
/// ```ebnf
/// In:
/// 'in' '(' AnyStringLiteral ')'
/// ;
/// 'in' '(' AnyStringLiteral ')' ;
/// ```
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Getters)]
@ -534,8 +530,7 @@ impl In {
/// Syntax Synopsis:
/// ```ebnf
/// On:
/// 'on' '(' AnyStringLiteral ')'
/// ;
/// 'on' '(' AnyStringLiteral ')' ;
/// ```
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Getters)]
@ -579,8 +574,7 @@ impl On {
/// Syntax Synopsis:
/// ```ebnf
/// Positioned:
/// 'positioned' '(' AnyStringLiteral ')'
/// ;
/// 'positioned' '(' AnyStringLiteral ')' ;
/// ```
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Getters)]
@ -624,8 +618,7 @@ impl Positioned {
/// Syntax Synopsis:
/// ```ebnf
/// Rotated:
/// 'rotated' '(' AnyStringLiteral ')'
/// ;
/// 'rotated' '(' AnyStringLiteral ')' ;
/// ```
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Getters)]
@ -669,8 +662,7 @@ impl Rotated {
/// Syntax Synopsis:
/// ```ebnf
/// Store:
/// 'store' '(' AnyStringLiteral ')'
/// ;
/// 'store' '(' AnyStringLiteral ')' ;
/// ```
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Getters)]
@ -714,8 +706,7 @@ impl Store {
/// Syntax Synopsis:
/// ```ebnf
/// Summon:
/// 'summon' '(' AnyStringLiteral ')'
/// ;
/// 'summon' '(' AnyStringLiteral ')' ;
/// ```
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Getters)]