Feature Request: Official BNF for RouterOS Scripting

Background
Many developers and tools, including modern AI/LLM models, can now understand and process formal syntax definitions. Having an official BNF (Backus-Naur Form) for ROS scripting would make it much easier to analyze, validate, and generate scripts accurately.

Why this would be useful

  • Better automation – A formal grammar would improve script generation and validation.
  • Improved code analysis – It would help with building linters and better debugging tools.
  • LLM support – AI-based assistants could provide accurate ROS scripting suggestions.
  • More consistent reference materials – A formal grammar would ensure that documentation, tutorials, and syntax guides stay accurate and aligned with the actual scripting language.

If Mikrotik published an official BNF for ROS scripting, it would help developers and tools, including LLMs, better understand and work with the scripting language. This would lead to smoother implementations of automation tools for network management and monitoring, enable better code analysis in external IDEs, and provide more useful learning resources for the Mikrotik community.

Technical perspective
If Mikrotik already uses Yacc/Bison or a similar parser generator internally, extracting a BNF shouldn’t be too much effort. Even if a different parsing approach is used, having a structured syntax definition would still be a valuable resource for the community.

Why should Mikrotik consider publishing an official BNF or EBNF for ROS scripting?
A formal grammar would make the language easier to work with, improve tooling support, and help AI-based tools provide more accurate assistance.

Would love to hear Mikrorik’s and the community’s thoughts on this!

1 Like

If you mean make Rextended jobs easier by providing us morons with a dictionary/lexicon as to what all the squiggles mean on a script. Hell yeah!
Or is this just CLI help?

Nowadays, almost all tools, like editors and IDEs like Notepad++, Emacs, and VS Code, can read BNF and highlight syntax errors.

Smart IDEs like Visual Studio, Eclipse, and JetBrains can also warn about logical errors, such as unassigned or misspelled variables, flawed conditions (if/then/else), infinite loops, or incorrect loop conditions. Visual Studio has plugins that let you develop and run ROS code directly from within the IDE.

Looks like your looking for a syntax checker while I am looking for a syntax teacher. :slight_smile:
No reason why we both cant be happy.

Haha, yeah! Some of those add-ons actually do both if you hook them up to Copilot. Win-win!

+1 upvote!

I was just thinking of making an MCP that can do this. There are complete dictionaries available on GH, but syntax? Not so much.

I’m still a bigger fan of an “official” language server provider (LSP), see https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/. This would seem more doable, since an LSP is just another REST API…so the LSP could just be part of REST API with different root URL. Since the existing /console/inspect largely provides some/most of the data needed (i.e. request=completion, request=highlight and request=syntax), it mainly be just re-packaging that data in the JSON-RPC used by LSP via RouterOS web server.

And with an RouterOS LSP every mainstream editor (like VSCode with CoPilot) have an authoritative source specific to [a] RouterOS device to accurately do completion and validate syntax, including extra features and be version-specific. And certainly CoPilot (or similar LLM connection to editor) does/should/cloud use the LSP to augment the LLM. e.g. not researched, but imagine some MCP that linked to an installed LSP exists.

Not saying BNF would not be NICE. Just not sure some text with BNF notation for scripting ALONE get you much, since it’s still needed incorporate in some TDB AI workflow to provide any value. The secondary problem is the BNF actually changes depending on version and if you have extra-packages. (Which is why RouterOS supporting being an LSP server be more useful, since it knows the entire AST for that device, and mostly available via /console/inspect data today)

While an LLM isn’t good at scripting. An LLM can generate BNF… so if BNF useful, an LLM could generated one… So for curiosity, just provided the free Claude LLM the MikroTik scripting doc as PDF and get some starting place in under 5 minutes. I suspect if used paid model, and/or iterated prompting a few more times – like providing the “Examples” and “Tips and Tricks” in docs – it like be even more refined. And certainly if MikroTIk did similar providing the theorized LEX/YACC (or whatever code implements scripting/CLI), I’d imagine it be MUCH closer… But just the PDF of scripting manual got me this, far from perfect but I suspect spending a few hours on it & spending money, an LLM would get pretty close to BNF for RouterOS.

But question be if the following BNF was 100% perfect (which it’s NOT), what does that get us*? (*without more work beyond the BNF)

# MikroTik RouterOS Scripting Language BNF

## General Structure

::= * ::= [] [] [] [] [] | "[" "]" ::= ":" | "/" ::= ["/" ]* ::= ::= ::= []+ ::= "=" ::= ";" | NEWLINE ``` ## Expressions ``` ::= | | | | | | ::= | | | | | | | | | | ::= ["-"] + ["." +] | "0x" + ::= "\"" * "\"" ::= | ::= "\\" | "\"" | "\n" | "\r" | "\t" | "\$" | "\?" | "\_" | "\a" | "\b" | "\f" | "\v" | "\xx" ::= "true" | "false" ::= "." "." "." ::= | | "1" | "2" | "25" ["0"-"5"] ::= "/" ::= | | "3" ["0"-"2"] ::= ::= "/" ::= | | "1" | "12" ::= "*" + ::= ::= "{" [ [";" ]*] "}" ::= | "=" ::= "nil" ::= "$" | "$" "(" ")" | "$" "[" "]" ::= "-" | "!" | "~" ::= ::= "+" | "-" | "*" | "/" | "%" | "<" | ">" | "=" | "<=" | ">=" | "!=" | "&&" | "and" | "||" | "or" | "in" | "|" | "^" | "&" | "<>" | "." | "," | "->" ::= "(" ")" ::= "[" "]" ::= "->" ``` ## Control Structures ``` ::= | | | | ::= ":if" "(" ")" "do=" "{" * "}" ["else=" "{" * "}"] ::= ":do" "{" * "}" "while=" "(" ")" ::= ":while" "(" ")" "do=" "{" * "}" ::= ":for" "from=" "to=" ["step=" ] "do=" "{" * "}" ::= ":foreach" ["," ] "in=" "do=" "{" * "}" ``` ## Scopes and Variable Declarations ``` ::= "{" * "}" ::= | ::= ":global" [] ::= ":local" [] ``` ## Commands ``` ::= ":set" [] ::= ":put" ::= ":log" ::= "debug" | "error" | "info" | "warning" ::= ":delay" ::= ":error" ::= ":onerror" "in=" "{" * "}" "do=" "{" * "}" ::= ":retry" "command=" ["delay=" ] ["max=" ] ::= ":return" [] ``` ## Common Terminal Symbols ``` ::= "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" ::= | "A" | "B" | "C" | "D" | "E" | "F" | "a" | "b" | "c" | "d" | "e" | "f" ::= [ | ]* | "\"" "\"" ::= "A" | "B" | ... | "Z" | "a" | "b" | ... | "z" ::= ["s" | "m" | "h" | "d" | "w"] ``` ## Function Definition (Workaround) ``` ::= ":global" "do=" "{" * "}" ::= "$" [] ``` ## Comments ``` ::= "#" * NEWLINE ``` ## Menu Commands ``` ::= | | | | | | | | | ::= "add" ::= "remove" ::= "enable" ::= "disable" ::= "set" ::= "get" ::= "print" []* ::= "as-value" | "brief" | "detail" | "count-only" | "file" | "follow" | "follow-only" | "from=" | "interval=" | "terse" | "value-list" | "without-paging" | "where" ::= "export" ["file=" ] ::= "edit" ::= "find" ``` ## Type Conversion Functions ``` ::= ":toarray" | ":tobool" | ":toid" | ":toip" | ":toip6" | ":tonum" | ":tostr" | ":totime" | ":tonsec" | ":tocrlf" | ":tolf" ``` ## Utility Functions ``` ::= ":len" | ":typeof" | ":pick" [] | ":find" [] | ":time" | ":timestamp" | ":resolve" | ":execute" | ":parse" | ":environment" "print" | ":nothing" | ":convert" | ":serialize" | ":deserialize" | ":range" | ":rndnum" "from=" "to=" | ":rndstr" ["from=" ] ["length=" ] | ":beep" ["frequency=" ] ["length=" ] ::= | "domain-name=" ["server=" ] ["port=" ] ["type=" ] ::= "any" | "any6" | "ipv4" | "ipv6" ::= "to=" ["transform=" ] ::= "base32" | "base64" | "bit-array-lsb" | "bit-array-msb" | "byte-array" | "hex" | "num" | "raw" | "url" ::= "lc" | "uc" | "lcfirst" | "ucfirst" | "crlf" | "ed25519-private-to-x25519-private" | "none" | "rot13" | "x25519-private-to-x25519-public" | "ed25519-private-to-ed25519-public" | "ed25519-public-to-x25519-public" | "md5" | "reverse" | "sha512" ::= ["value=" ] ["to=" ] [] ::= "json" | "dsv" ::= ["delimiter=" ] ["order=" ] ["options=" ] ["file-name=" ] ::= ["," ]* ::= "json.pretty" | "json.no-string-conversion" | "dsv.wrap-strings" | "dsv.ignore-size" | "dsv.remap" ::= ["value=" ] ["from=" ] [] ::= "json" | "dsv" ::= ["delimiter=" ] ["options=" ] ``` ```
;`¯\_(ツ)_/¯`
; RouterOS Complete BNF Grammar Specification
; Based on analysis of real RouterOS scripts
; Version: 7.15+ (includes all syntax found in routeros-scripts repository)

; ============================================================================
; PROGRAM STRUCTURE
; ============================================================================

<program> ::= <shebang_line> <statement_list> | <statement_list>
<shebang_line> ::= "#!rsc by RouterOS" <newline>

<statement_list> ::= <empty> | <statement> | <statement> <separator> <statement_list>
<separator> ::= ";" | <newline> | ";" <newline>

; Statements can span multiple lines with backslash continuation
<statement> ::= <single_statement> | <continued_statement>
<single_statement> ::= <script_command> | <navigation_command> | <comment> | <empty>
<continued_statement> ::= <statement_part> "\\" <newline> <statement>
<statement_part> ::= <script_command> | <navigation_command> | <partial_expression>

; ============================================================================
; COMMENTS
; ============================================================================

<comment> ::= "#" <comment_text>
<comment_text> ::= <any_char_except_newline> <comment_text> | <empty>

; ============================================================================
; NAVIGATION AND COMMANDS
; ============================================================================

<navigation_command> ::= <path> | <path> <ws> <command>
<path> ::= "/" | "/" <menu_path> | ".." | "."
<menu_path> ::= <path_segment> | <path_segment> "/" <menu_path> |
                <path_segment> <ws> <menu_path>  ; Space-separated paths
<path_segment> ::= <identifier> | <identifier> "-" <path_segment>

<command> ::= <command_name> <parameters>
<command_name> ::= "add" | "set" | "print" | "find" | "remove" | "enable" | "disable" |
                   "export" | "import" | "edit" | "move" | "comment" | "unset" |
                   "reset" | "get" | "monitor" | "check" | "make" | "upgrade" |
                   "save" | "backup" | "send" | "run" | <identifier>

<parameters> ::= <empty> | <ws> <parameter_list>
<parameter_list> ::= <parameter> | <parameter> <ws> <parameter_list>
<parameter> ::= <named_parameter> | <where_clause> | <positional_parameter>
<named_parameter> ::= <param_name> "=" <expression>
<where_clause> ::= "where" <ws> <expression>
<positional_parameter> ::= <expression>
; Any token can be a parameter name when followed by =
<param_name> ::= <any_token>

; ============================================================================
; SCRIPT COMMANDS
; ============================================================================

<script_command> ::= ":" <script_statement>
<script_statement> ::= <variable_declaration> | <variable_assignment> |
                       <control_structure> | <builtin_command> |
                       <function_definition> | <function_call> |
                       <error_handling>

; Variable declarations
<variable_declaration> ::= <var_scope> <ws> <identifier> |
                          <var_scope> <ws> <identifier> <ws> <expression>
<var_scope> ::= "local" | "global"

; Variable assignment
<variable_assignment> ::= "set" <ws> <identifier> <ws> <expression> |
                         "set" <ws> <variable_ref> <ws> <expression>

; Function definition (special form of global variable)
<function_definition> ::= "global" <ws> <identifier> <ws> "do" "=" <code_block>

; ============================================================================
; CONTROL STRUCTURES
; ============================================================================

<control_structure> ::= <if_statement> | <for_loop> | <foreach_loop> |
                       <while_loop> | <do_block>

<if_statement> ::= "if" <ws> "(" <expression> ")" <ws> "do" "=" <code_block> |
                   "if" <ws> "(" <expression> ")" <ws> "do" "=" <code_block> <ws> "else" "=" <code_block>

<for_loop> ::= "for" <ws> <identifier> <ws> <for_params> <ws> "do" "=" <code_block>
<for_params> ::= "from" "=" <expression> <ws> "to" "=" <expression> |
                 "from" "=" <expression> <ws> "to" "=" <expression> <ws> "step" "=" <expression>

<foreach_loop> ::= "foreach" <ws> <identifier> <ws> "in" "=" <expression> <ws> "do" "=" <code_block>

<while_loop> ::= "while" <ws> "(" <expression> ")" <ws> "do" "=" <code_block>

<do_block> ::= "do" <ws> <code_block> |
               "do" <ws> <code_block> <ws> "on-error" "=" <code_block>

<code_block> ::= "{" <statement_list> "}"

; ============================================================================
; ERROR HANDLING
; ============================================================================

<error_handling> ::= <onerror_statement> | <retry_statement>

; :onerror is different from on-error clause
<onerror_statement> ::= "onerror" <ws> <identifier> <ws> <code_block>

<retry_statement> ::= "retry" <ws> <code_block> |
                     "retry" <ws> <code_block> <ws> <retry_params>
<retry_params> ::= <retry_param> | <retry_param> <ws> <retry_params>
<retry_param> ::= "delay" "=" <expression> | "max" "=" <expression>

; ============================================================================
; BUILT-IN COMMANDS AND FUNCTIONS
; ============================================================================

<builtin_command> ::= <put_command> | <log_command> | <error_command> |
                      <delay_command> | <return_command> | <beep_command> |
                      <execute_command> | <jobname_command>

<put_command> ::= "put" <ws> <expression>
<log_command> ::= "log" <ws> <log_level> <ws> <expression> | "log" <ws> <expression>
<log_level> ::= "debug" | "info" | "warning" | "error"
<error_command> ::= "error" <ws> <expression> | "error" <ws> "(" <expression> ")"
<delay_command> ::= "delay" <ws> <expression>
<return_command> ::= "return" | "return" <ws> <expression>
<beep_command> ::= "beep" <ws> <beep_params>
<beep_params> ::= <parameter_list>
<execute_command> ::= "execute" <ws> <expression>
<jobname_command> ::= "jobname"  ; Returns current job name

; Function calls (without command substitution brackets)
<function_call> ::= <function_name> <ws> <function_args> | <function_name>
<function_name> ::= <identifier> | "$" <identifier>  ; User-defined functions
<function_args> ::= <expression_list> | <named_args_list>
<named_args_list> ::= <named_arg> | <named_arg> <ws> <named_args_list>
<named_arg> ::= <identifier> "=" <expression>

; ============================================================================
; EXPRESSIONS - With proper precedence
; ============================================================================

<expression> ::= <ternary_expression>

; Ternary operator (RouterOS 7+)
<ternary_expression> ::= <logical_or_expression> |
                        <logical_or_expression> <ws> "?" <ws> <expression> <ws> ":" <ws> <expression>

; Logical operators (lowest precedence after ternary)
<logical_or_expression> ::= <logical_and_expression> |
                           <logical_or_expression> <ws> "||" <ws> <logical_and_expression> |
                           <logical_or_expression> <ws> "or" <ws> <logical_and_expression>

<logical_and_expression> ::= <bitwise_or_expression> |
                            <logical_and_expression> <ws> "&&" <ws> <bitwise_or_expression> |
                            <logical_and_expression> <ws> "and" <ws> <bitwise_or_expression>

; Bitwise operators
<bitwise_or_expression> ::= <bitwise_xor_expression> |
                           <bitwise_or_expression> <ws> "|" <ws> <bitwise_xor_expression>

<bitwise_xor_expression> ::= <bitwise_and_expression> |
                            <bitwise_xor_expression> <ws> "^" <ws> <bitwise_and_expression>

<bitwise_and_expression> ::= <in_expression> |
                            <bitwise_and_expression> <ws> "&" <ws> <in_expression>

; Membership operator
<in_expression> ::= <relational_expression> |
                    <relational_expression> <ws> "in" <ws> <relational_expression>

; Relational operators
<relational_expression> ::= <shift_expression> |
                           <relational_expression> <ws> <relational_op> <ws> <shift_expression>
<relational_op> ::= "=" | "!=" | "<" | ">" | "<=" | ">=" | "~"

; Bit shift operators
<shift_expression> ::= <concat_expression> |
                      <shift_expression> <ws> "<<" <ws> <concat_expression> |
                      <shift_expression> <ws> ">>" <ws> <concat_expression>

; String concatenation
<concat_expression> ::= <additive_expression> |
                        <concat_expression> <ws> "." <ws> <additive_expression>

; Arithmetic operators
<additive_expression> ::= <multiplicative_expression> |
                         <additive_expression> <ws> <additive_op> <ws> <multiplicative_expression>
<additive_op> ::= "+" | "-"

<multiplicative_expression> ::= <unary_expression> |
                               <multiplicative_expression> <ws> <multiplicative_op> <ws> <unary_expression>
<multiplicative_op> ::= "*" | "/" | "%"

<unary_expression> ::= <postfix_expression> | <unary_op> <unary_expression>
<unary_op> ::= "!" | "~" | "-" | "not"

<postfix_expression> ::= <primary_expression>

; Array concatenation with comma
<comma_expression> ::= <logical_or_expression> |
                      <comma_expression> "," <logical_or_expression>

<primary_expression> ::= <literal> | <variable_ref> | <identifier> |
                        <array_literal> | <command_substitution> |
                        <function_substitution> | <parenthesized_expression> |
                        <user_function_call>

<parenthesized_expression> ::= "(" <expression> ")"

; ============================================================================
; COMMAND AND FUNCTION SUBSTITUTION
; ============================================================================

; Command substitution - executes commands
<command_substitution> ::= "[" <navigation_command> "]" | "[" <ws> "]"

; Function substitution - calls built-in functions
<function_substitution> ::= "[" ":" <builtin_function> "]"
<builtin_function> ::= <conversion_function> | <string_function> |
                       <utility_function> | <jobname_function>

<conversion_function> ::= "tonum" <ws> <expression> |
                         "tostr" <ws> <expression> |
                         "toarray" <ws> <expression> |
                         "tobool" <ws> <expression> |
                         "toip" <ws> <expression> |
                         "toip6" <ws> <expression> |
                         "totime" <ws> <expression> |
                         "toid" <ws> <expression> |
                         "convert" <ws> <convert_params>

<convert_params> ::= <expression> <ws> "from" "=" <expression> <ws> "to" "=" <expression>

<string_function> ::= "pick" <ws> <expression> <ws> <expression> <ws> <expression> |
                      "pick" <ws> <expression> <ws> <expression> |
                      "find" <ws> <expression> <ws> <expression> |
                      "find" <ws> <expression> <ws> <expression> <ws> <expression> |
                      "len" <ws> <expression>

<utility_function> ::= "resolve" <ws> <expression> |
                       "resolve" <ws> <expression> <ws> <named_args_list> |
                       "rndnum" <ws> "from" "=" <expression> <ws> "to" "=" <expression> |
                       "rndnum" <ws> "to" "=" <expression> |
                       "rndstr" <ws> <expression> |
                       "rndstr" <ws> <expression> <ws> "from" "=" <expression> |
                       "parse" <ws> <expression> |
                       "serialize" <ws> <expression> |
                       "deserialize" <ws> <expression> |
                       "typeof" <ws> <expression> |
                       "time" | "timestamp" |
                       "execute" <ws> <expression>

<jobname_function> ::= "jobname"

; User-defined function call
<user_function_call> ::= "$" <identifier> <ws> <expression_list> |
                         "$" <identifier> |
                         "[" "$" <identifier> <ws> <expression_list> "]" |
                         "[" "$" <identifier> "]"

; ============================================================================
; LITERALS AND VALUES
; ============================================================================

<literal> ::= <string> | <number> | <boolean> | <ip_address> | <time_value> | <nothing>

<string> ::= '"' <string_content> '"'
<string_content> ::= <empty> | <string_char> <string_content>
<string_char> ::= <any_char_except_quote_backslash> | <escape_sequence>
<escape_sequence> ::= "\\" <escapable>
<escapable> ::= '"' | "\\" | "n" | "r" | "t" | "$" | <any_char>

<number> ::= <integer> | <decimal> | <hex_number>
<integer> ::= <digit_sequence>
<decimal> ::= <digit_sequence> "." <digit_sequence>
<hex_number> ::= "0x" <hex_digit_sequence>
<digit_sequence> ::= <digit> | <digit> <digit_sequence>
<hex_digit_sequence> ::= <hex_digit> | <hex_digit> <hex_digit_sequence>

<boolean> ::= "yes" | "no" | "true" | "false"

<ip_address> ::= <ipv4_address> | <ipv4_prefix> | <ipv6_address> | <ipv6_prefix>
<ipv4_address> ::= <octet> "." <octet> "." <octet> "." <octet>
<ipv4_prefix> ::= <ipv4_address> "/" <prefix_length>
<ipv6_address> ::= <ipv6_format>
<ipv6_prefix> ::= <ipv6_address> "/" <prefix_length>
<octet> ::= <digit> | <digit> <digit> | <digit> <digit> <digit>
<prefix_length> ::= <digit> | <digit> <digit> | "1" <digit> <digit>
<ipv6_format> ::= <hex_digit> <ipv6_format_tail>
<ipv6_format_tail> ::= ":" <ipv6_format> | "::" <ipv6_format> | <hex_digit> <ipv6_format_tail> | <empty>

<time_value> ::= <number> <time_unit>
<time_unit> ::= "s" | "ms" | "us" | "m" | "h" | "d" | "w"

<nothing> ::= "nothing" | "nil"

; ============================================================================
; ARRAYS
; ============================================================================

<array_literal> ::= "{" <array_elements> "}" | "{" "}"
<array_elements> ::= <array_element> | <array_element> ";" <array_elements>
<array_element> ::= <expression> | <key_value_pair>
<key_value_pair> ::= <string> "=" <expression> | <identifier> "=" <expression>

; ============================================================================
; VARIABLES AND IDENTIFIERS
; ============================================================================

<variable_ref> ::= "$" <identifier> | "$" <identifier> <property_access>
<property_access> ::= "->" <identifier> | "->" <identifier> <property_access>

<identifier> ::= <letter> <identifier_tail> | <letter>
<identifier_tail> ::= <identifier_char> <identifier_tail> | <identifier_char>
<identifier_char> ::= <letter> | <digit> | "-" | "_"

<expression_list> ::= <expression> | <expression> <ws> <expression_list>

; ============================================================================
; BASIC ELEMENTS
; ============================================================================

<letter> ::= <lowercase> | <uppercase>
<lowercase> ::= "a" | "b" | "c" | "d" | "e" | "f" | "g" | "h" | "i" | "j" |
                "k" | "l" | "m" | "n" | "o" | "p" | "q" | "r" | "s" | "t" |
                "u" | "v" | "w" | "x" | "y" | "z"
<uppercase> ::= "A" | "B" | "C" | "D" | "E" | "F" | "G" | "H" | "I" | "J" |
                "K" | "L" | "M" | "N" | "O" | "P" | "Q" | "R" | "S" | "T" |
                "U" | "V" | "W" | "X" | "Y" | "Z"
<digit> ::= "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"
<hex_digit> ::= <digit> | "a" | "b" | "c" | "d" | "e" | "f" |
                         "A" | "B" | "C" | "D" | "E" | "F"

<ws> ::= <ws_char> | <ws_char> <ws>
<ws_char> ::= " " | "\t"
<newline> ::= "\n" | "\r\n"
<empty> ::= ""

<any_token> ::= <identifier> | <keyword> | <operator_keyword>
<keyword> ::= "local" | "global" | "set" | "if" | "do" | "else" | "for" | "from" |
              "to" | "step" | "foreach" | "in" | "while" | "return" | "put" |
              "log" | "error" | "delay" | "beep" | "where" | "and" | "or" | "not"
<operator_keyword> ::= "in" | "and" | "or" | "not"

<any_char_except_newline> ::= <any_printable_char> | <ws_char>
<any_char_except_quote_backslash> ::= <letter> | <digit> | <special_char> | <ws_char>
<special_char> ::= "!" | "@" | "#" | "$" | "%" | "^" | "&" | "*" | "(" | ")" |
                   "-" | "_" | "=" | "+" | "[" | "]" | "{" | "}" | ";" | ":" |
                   "'" | "," | "." | "/" | "<" | ">" | "?" | "|" | "~" | "`"
<any_char> ::= <any_char_except_newline> | <newline>
<any_printable_char> ::= <letter> | <digit> | <special_char>

<partial_expression> ::= <primary_expression> | <partial_expression> <ws> <operator> <ws>
<operator> ::= <arithmetic_op> | <relational_op> | <logical_op> | <bitwise_op>
<logical_op> ::= "||" | "&&" | "and" | "or"
<bitwise_op> ::= "&" | "|" | "^" | "<<" | ">>"
<arithmetic_op> ::= "+" | "-" | "*" | "/" | "%" | "."

; ============================================================================
; KEY ADDITIONS FOR REAL SCRIPTS:
; ============================================================================
; 1. Shebang line support (#!rsc by RouterOS)
; 2. Line continuation with backslash (\)
; 3. :onerror command (different from on-error clause)
; 4. :retry command with delay= and max= parameters
; 5. || and && operators (in addition to 'or' and 'and')
; 6. :jobname command
; 7. Bitwise operators (|, &, ^, <<, >>)
; 8. :execute command
; 9. Ternary operator (? :)
; 10. User function calls with $ prefix
; 11. Any token can be parameter name before =
; 12. Hex numbers (0x...)
; 13. Space-separated menu paths (/ip arp, /tool e-mail)
; 14. Parentheses around error messages
; 15. Empty command substitution []%```
1 Like