🧬 RouterOS LSP for better syntax checking & command completion in editors like VSCode & NeoVim

Now we need to use this to reverse engineer the grammar so we can parse source code into AST without live connection to an instance of RouterOS.

LOL, apparently the forum’s ```routeros marks use a pretty poor coloring scheme and syntax scheme too. While not the whole story in β€œstatic schemas/AST”, for example /console/inspect gets you all the command, directory (like ip, interface, etc), and arguments. So those could always be used to β€œupdate” a static grammar with the current set of things β€” perhaps with curation since there are 70k worth of keywords for example.

See this thread for one example of potentially β€œaugmenting” a static/regex .rsc grammar with /console/inspect request=child data.

I updated the RouterOS LSP. The GitHub CHANGELOG.md details the changes. While I should do another animated GIFs - here are some screenshots showing it working in VSCode for Web & NeoVim too showing the newer features.

Download RouterOS LSP

Visual Studio Code use "Extensions"

This will like to the VSCode Extension in "Marketplace" (free) with the "Install" link for VSCode:

https://marketplace.visualstudio.com/items?itemName=TIKOCI.lsp-routeros-ts

or you can just search in VSCode for "RouterOS LSP" too

GitHub Release (for NeoVim) -

See README.md since it more complex... New downloads are at:

some notes...

Scripts with unescaped UTF-8 characters are not recommended...

In short, use Windows1252 encoding. Using "\74\23\12" is okay, but have something like β€” (em dash) or :water_pistol: (emojis) etc... may causes problems – even if they are in comments.

Still some problems with UTF-8 in comments – close/reopen will generally fix things with UTF-8. But it's complex situation to handle since VSCode "Position" is, essentially, UTF-16...but "tokens" from RouterOS for colors/diagnostics uses UTF-8 - which causes position not align always in some cases. The CHANGELOG details them, which is also shown in VSCode page for RouterOS LSP Extension.

Screenshot 2025-06-23 at 11.55.47 AM

Settings has a "debug level"...

If you have troubles with the LSP, change the level to "verbose". Messages go to VSCode's "Output" panel under "RouterOS LSP" from dropdown:

New Features

"Document Symbols" (for :local and :global)

In VSCode they appear in "Outline" from Explorer sidebar:

Multiple found variables should be groupable and kinda show the declaration from usage.

Completion have icons

... and "help text" if available from /console/inspect

VSCode Only "Walkthrough" shown on "Welcome" page

This is WIP but works to direct you to provide the need RouterOS "baseUrl" and account info:


Experimental TikBook RouterOS Notebook works too

This uses a related extension "TikBook" discussed about in this thread:

NeoVim still works...

Shown with the $PIANO script:

and also shows the "help text" add in this release:

If I may (and if it is possible) the thing that personally I have a lot of difficulties with when attempting to read/parse a script are the parenthesis/brackets.

I wonder if there could be a way to better distinguish (… ) from [… ] and {… } (particulalrly the last two (which is usually but not always mitigated by indentation) but also square and round can be confused.

Having them with different colours?
Or using for them bold/a different font size?

Like this?
https://marketplace.visualstudio.com/items?itemName=MadsKristensen.RainbowBraces

P.S.: the stupid Discourse thinks that opening and closing square brackets without space in the middle (or even with it) means β€œAhh, the user wants to draw a little square, let me do it: !”
If it was adequate, it would draw a small circle with () :roll_eyes: (and why not a diamond/rhombus with {}?).

The newest version may be improved a bit actually. VSCode already does the same as β€œrainbow braces” does (AFAIK, using VSCode default theme). I tried in VSCode for Web, where I have no plugins other than this one.

I add some extra [ ] on one line to show it, but it does alternate colors for brackets correctly:


Screenshot 2025-06-24 at 6.07.06 AM

I did have some β€œcustom folding regex” borrowed from another RouterOS plugin, but in current VSCode… telling it RouterOS using {, [, ( braces was enough. No RegExp to define folding, which is also used in bracket matching…

This fixed issues where folding was not being detected right (which also mean bracket weren’t either), so may fixed color NOT alternating too. #DoingLessIsDoingMore

And you do mess up braces… it now lights up like a Christmas Tree in latest LSP extension version β€” with a warning flagged to end of file past the originally found error from /console/inspect

That all being said, there is β€œmore” than can be done. Currently all the bracket-things are classified as syntax-meta which matches RouterOS scheme.

But the semantic token (used β€œhint” VSCode on color) do offer β€œmodifiers”. I currently don’t provide any, but I breakup the syntax-meta into sub-categories for different types β€œmeta” chapters, which include the brackets and = etc. In VSCode (or any LSP editor) you could be able to explicitly set the colors differently on [ vs { etc. based on the token + modifier .

Whole different set of problems here… [ ] are used for links in markdown, so yeah β€œraw cut-and-paste” in post may have odd results. And the highlight.js plugin for RouterOS used in the ```routeros code blocks only really knows V6 syntax, so it too can fail badly. Further doubt most folks think to add routeros to the end of ```…

MikroTik should just write a small discourse plugin that calls same /console/inspect request=highlight this does and set the colors on backend. Like it does when β€œunfurling” URL on a line. No highlight.js syntax/grammar updates and β€œperfect” colors (i.e. matching CLI).

Also, in VSCode, you should be able to β€œjump” to matching bracket with

⌘ + Shift + \

To see what matches any brace – that all should match it right AFAIK.
Check VSCode’s_ Help > Keyboard Shortcut Reference if not using Mac to find the shortcut for this

RouterOS LSP 0.5.x Released

The RouterOS LSP has been updated with many small improvements to backend, and some new "commands" and a "watchdog" too. See CHANGELOG.md for details.

It is published on the VS Code marketplace for download within VS Code. As well as on Open-VSIX.

Please do try it out and report any feedback in this thread or open a GitHub issue or pull request is okay too

The RouterOS syntax coloring and completion is same, however both the backend and "UI" have been updated. Here are some some screenshots of the new 0.5.x features:

VS Code Command Palette

Access the LSP settings, logs, aliveness test (part of "watchdog"), as well as command to refresh colors ("Refresh emantic tokens"), and a way to apply to colors to user settings so they can be customized ("Apply RouterOS Syntax Colors")

Settings Changes

The timeout is now exposed in settings. And an option to allow future TikBook versions to "override" the LSP connection settings, to use TikBook extension's settings instead.

Watchdog (liveness check)

The "watchdog" in action... If RouterOS LSP cannot connect when it first loads, an error with appear to allow you to "fix it". The message are "raw" so something like status code 401 means bad password, but plan to use "friendly" messages at some point.

If successful, a small message appears with a RouterOS file is first loaded (which then causes RouterOS LSP to be loaded – extension start only when needed in VS Code):

LSP Core

The backend changes do not have screenshots - but there is good improvement in at unnecessary recalculation of tokens etc. These changes effect all LSP clients, not just VSCode – while the commands are VS Code only features.

On the subject of TikBook (i.e. there is now a settings for it in RouterOS LSP extension)...

TikBook Update Coming Soon

I'm working on an update for TikBook for RouterOS for VS Code with a revised serialization format to allow for both a notebook that is a valid RouterOS script & one that allow ```routeros blocks in Markdown files to be "Run". Conversion between the Markdown-first or RouterOS-first notebooks is possible. I'll start a new thread about when I release an update.

TikBook Teaser

Hey man, thanks so much for making this tool! :partying_face: Today was my first time ever using VSCode for RouterOS Script, so I guess I was just very lucky that you made this LSP just recently because it would have been quite a different experience otherwise.

I noticed some minor issues, will create reports in your Github Repo.

RouterOS LSP 0.5.4 Released

I updated the RouterOS LSP last week to version 0.5.4. In the new version, among other changes, is better error handling of connection issues. As well as additional support needed for TikBook...

Improved version of TikBook also released

This release has many, many improvements and changes from the prototype discussed in this thread. The file format has changed, so if you tried the 0.1.x version, you'll have to manually fix up the files (please reply if you need help).

To keep things separate, I created a new topic in "3rd Party Tools" for TikBook extension discussed above.

fractals-rendered

copilot-ip-add

Nice work. Thnx

VS Code and /app YAML

Eventually the RouterOS LSP will load the schema (at least for VSCode), but for now it's manual process

For "LSP-like" validation of new 7.22beta "Custom /app"'s YAML format in VSCode (and potentially other editors), see:

"Stable" Release 0.6.0

I have "promoted" the previous 0.5.4 "testing" (pre-release in VS Code terms) to a "stable" version of 0.6.0.

There are no significatant changes, other than minor dependacy updates based on bun audit. Even minor numbers are "stable", while odd numbers are "testing" builds, thus only the minor version change

But if you have any trouble, please report here or in GitHub Issues.

For VSCode, in most cases, the extension will auto-update. If not, just Install from VSCode Marketplace Extentions.

For other LSP-enabled editors, download RouterOS LSP Server from GitHub Releases:

TikBook also updated to "stable"...

The TikBook companion extension was also updated to "stable". I plan on adding some new features, including allowing direct editing of /system/scripts from VSCode, among others. For background, VSCode-only features always use the TikBook extension. RouterOS LSP was designed to work with any LSP-enabled editor, so VSCode-specific features are never added in the core LSP server..

https://marketplace.visualstudio.com/items?itemName=TIKOCI.tikbook

Interesting Factoid: Keep your scripts under 28K.

I've been slowing adding tests to RouterOS LSP. Undergirding all actual syntax checking/colors in the LSP /console/inspect request=completion discussed in this thread. This has worked well... but sometime it is slow with very large scripts files, and it seemed kinda random to me exactly what was going on. I ran some tests against a set of scripts from @eworm collection, my forum posts, as well as "synthetic" data (like a large script filled with all comments, for example).

28K is a magic number. Past 28K this, the robots tell me, "go quadriatic", luckily it did some charts... The test RouterOS is CHR with 1GB RAM, 4 Intel CPU cores, no other traffic than REST API to /console/inspect.

β–Έ Profiling: synthetic-comments
    0.1KB      2ms  β–ˆ
    0.3KB      2ms  β–ˆ
    0.5KB      3ms  β–ˆ
    1.0KB      6ms  β–ˆ
    2.0KB     11ms  β–ˆ
    3.0KB     17ms  β–ˆ
    4.0KB     21ms  β–ˆ
    6.0KB     36ms  β–ˆ
    8.0KB     63ms  β–ˆ
   10.0KB     78ms  β–ˆβ–ˆ
   12.0KB    105ms  β–ˆβ–ˆ
   16.0KB    162ms  β–ˆβ–ˆβ–ˆ
   20.0KB    227ms  β–ˆβ–ˆβ–ˆβ–ˆβ–ˆ
   24.0KB    330ms  β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ
   28.0KB   1494ms  β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ
   32.0KB   2199ms  β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ

β–Έ Profiling: synthetic-commands
    0.1KB      2ms  β–ˆ
    0.3KB      3ms  β–ˆ
    0.5KB      5ms  β–ˆ
    1.0KB      8ms  β–ˆ
    2.0KB     16ms  β–ˆ
    3.0KB     26ms  β–ˆ
    4.0KB     33ms  β–ˆ
    6.0KB     59ms  β–ˆ
    8.0KB     96ms  β–ˆβ–ˆ
   10.0KB    116ms  β–ˆβ–ˆ
   12.0KB    142ms  β–ˆβ–ˆβ–ˆ
   16.0KB    226ms  β–ˆβ–ˆβ–ˆβ–ˆβ–ˆ
   20.0KB    310ms  β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ
   24.0KB    418ms  β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ
   28.0KB   1777ms  β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ
   32.0KB   2351ms  β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ

β–Έ Profiling: synthetic-mixed-paths
    0.1KB      2ms  β–ˆ
    0.3KB      3ms  β–ˆ
    0.5KB      4ms  β–ˆ
    1.0KB      8ms  β–ˆ
    2.0KB     16ms  β–ˆ
    3.0KB     25ms  β–ˆ
    4.0KB     33ms  β–ˆ
    6.0KB     54ms  β–ˆ
    8.0KB    102ms  β–ˆβ–ˆ
   10.0KB    113ms  β–ˆβ–ˆ
   12.0KB    144ms  β–ˆβ–ˆβ–ˆ
   16.0KB    222ms  β–ˆβ–ˆβ–ˆβ–ˆ
   20.0KB    322ms  β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ
   24.0KB    459ms  β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ
   28.0KB   2098ms  β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ
   32.0KB   4557ms  β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ

β–Έ Profiling: synthetic-complex
    0.1KB      5ms  β–ˆ
    0.3KB      7ms  β–ˆ
    0.5KB     14ms  β–ˆ
    1.0KB     24ms  β–ˆ
    2.0KB     46ms  β–ˆ
    3.0KB     72ms  β–ˆ
    4.0KB    110ms  β–ˆβ–ˆ
    6.0KB    167ms  β–ˆβ–ˆβ–ˆ
    8.0KB    242ms  β–ˆβ–ˆβ–ˆβ–ˆβ–ˆ
   10.0KB    335ms  β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ
   12.0KB    528ms  β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ
   16.0KB    755ms  β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ
   20.0KB   1027ms  β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ
   24.0KB   1398ms  β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ
   28.0KB   5158ms  β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ
   32.0KB   6022ms  β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ

β–Έ Profiling: eworm/global-functions.rsc
    0.1KB      3ms  β–ˆ
    0.3KB      5ms  β–ˆ
    0.5KB     10ms  β–ˆ
    1.0KB     22ms  β–ˆ
    2.0KB     53ms  β–ˆ
    3.0KB     81ms  β–ˆβ–ˆ
    4.0KB    134ms  β–ˆβ–ˆβ–ˆ
    6.0KB    197ms  β–ˆβ–ˆβ–ˆβ–ˆ
    8.0KB    287ms  β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ
   10.0KB    392ms  β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ
   12.0KB    458ms  β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ
   16.0KB    627ms  β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ
   20.0KB    943ms  β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ
   24.0KB   1198ms  β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ
   28.0KB   4053ms  β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ
   32.0KB   3721ms  β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ

β–Έ Profiling: edge-cases/oversize-32k.rsc
    0.1KB      2ms  β–ˆ
    0.3KB      4ms  β–ˆ
    0.5KB      6ms  β–ˆ
    1.0KB     10ms  β–ˆ
    2.0KB     22ms  β–ˆ
    3.0KB     34ms  β–ˆ
    4.0KB     47ms  β–ˆ
    6.0KB     77ms  β–ˆβ–ˆ
    8.0KB    107ms  β–ˆβ–ˆ
   10.0KB    146ms  β–ˆβ–ˆβ–ˆ
   12.0KB    200ms  β–ˆβ–ˆβ–ˆβ–ˆ
   16.0KB    280ms  β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ
   20.0KB    440ms  β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ
   24.0KB    571ms  β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ
   28.0KB   2163ms  β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ
   32.0KB   2441ms  β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ

β–Έ Profiling: complex/piano.rsc
    0.1KB      2ms  β–ˆ
    0.3KB      3ms  β–ˆ
    0.5KB      5ms  β–ˆ
    1.0KB     10ms  β–ˆ
    2.0KB     24ms  β–ˆ
    3.0KB     38ms  β–ˆ
    4.0KB     53ms  β–ˆ
    6.0KB     86ms  β–ˆβ–ˆ
    8.0KB    120ms  β–ˆβ–ˆ
   10.0KB    160ms  β–ˆβ–ˆβ–ˆ

────────────────────────────────────────────────────────────────────────────────
  RouterOS Highlight API β€” Performance Profile
  CHR: http://192.168.74.150  β€’  3 runs/point  β€’  2 warmup runs
────────────────────────────────────────────────────────────────────────────────

## Scaling Analysis
  Script                         Profile        Model                          RΒ²   ms/KB
  ────────────────────────────── ────────────── ────────────────────────── ────── ───────
  synthetic-comments             comments       quadratic (superlinear)     0.917    48.1
  synthetic-commands             single-path    quadratic (superlinear)     0.920    54.2
  synthetic-mixed-paths          multi-path     quadratic (superlinear)     0.877    86.4
  synthetic-complex              scripting      quadratic (superlinear)     0.928   149.8
  eworm/global-functions.rsc     scripting      quadratic (superlinear)     0.904   105.7
  edge-cases/oversize-32k.rsc    single-path    quadratic (superlinear)     0.924    61.5
  complex/piano.rsc              scripting      linear                      0.994    15.8

## synthetic-comments
  Pure comment lines β€” minimal tokenization work
  Syntax profile: comments

     Size   Median     Min     Max Curve
  ─────── ──────── ─────── ─────── ──────────────────────────────
     0.1K       2ms      2ms      2ms β–ˆ
     0.3K       2ms      2ms      3ms β–ˆ
     0.5K       3ms      3ms      3ms β–ˆ
     1.0K       6ms      5ms      6ms β–ˆ
     2.0K      11ms     10ms     13ms β–ˆ
     3.0K      17ms     16ms     18ms β–ˆ
     4.0K      21ms     21ms     22ms β–ˆ
     6.0K      36ms     36ms     39ms β–ˆ
     8.0K      63ms     56ms     67ms β–ˆ
    10.0K      78ms     76ms     78ms β–ˆβ–ˆ
    12.0K     105ms    102ms    111ms β–ˆβ–ˆ
    16.0K     162ms    160ms    162ms β–ˆβ–ˆβ–ˆ
    20.0K     227ms    225ms    239ms β–ˆβ–ˆβ–ˆβ–ˆβ–ˆ
    24.0K     330ms    319ms    353ms β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ
    28.0K    1494ms   1475ms   1519ms β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ
    32.0K    2199ms   2119ms   2471ms β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ

## synthetic-commands
  Repetitive /ip firewall address-list add β€” same path each line
  Syntax profile: single-path

     Size   Median     Min     Max Curve
  ─────── ──────── ─────── ─────── ──────────────────────────────
     0.1K       2ms      2ms      2ms β–ˆ
     0.3K       3ms      2ms      3ms β–ˆ
     0.5K       5ms      5ms      5ms β–ˆ
     1.0K       8ms      7ms      8ms β–ˆ
     2.0K      16ms     15ms     16ms β–ˆ
     3.0K      26ms     25ms     28ms β–ˆ
     4.0K      33ms     31ms     33ms β–ˆ
     6.0K      59ms     57ms     66ms β–ˆ
     8.0K      96ms     83ms     96ms β–ˆβ–ˆ
    10.0K     116ms    107ms    117ms β–ˆβ–ˆ
    12.0K     142ms    142ms    144ms β–ˆβ–ˆβ–ˆ
    16.0K     226ms    224ms    230ms β–ˆβ–ˆβ–ˆβ–ˆβ–ˆ
    20.0K     310ms    309ms    317ms β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ
    24.0K     418ms    416ms    423ms β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ
    28.0K    1777ms   1768ms   1793ms β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ
    32.0K    2351ms   2276ms   2373ms β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ

## synthetic-mixed-paths
  Mix of /ip, /interface, /queue, /system paths β€” wide path resolution
  Syntax profile: multi-path

     Size   Median     Min     Max Curve
  ─────── ──────── ─────── ─────── ──────────────────────────────
     0.1K       2ms      2ms      2ms β–ˆ
     0.3K       3ms      3ms      3ms β–ˆ
     0.5K       4ms      4ms      4ms β–ˆ
     1.0K       8ms      8ms      8ms β–ˆ
     2.0K      16ms     15ms     17ms β–ˆ
     3.0K      25ms     24ms     25ms β–ˆ
     4.0K      33ms     33ms     34ms β–ˆ
     6.0K      54ms     54ms     54ms β–ˆ
     8.0K     102ms     80ms    132ms β–ˆβ–ˆ
    10.0K     113ms    111ms    117ms β–ˆβ–ˆ
    12.0K     144ms    143ms    144ms β–ˆβ–ˆβ–ˆ
    16.0K     222ms    221ms    222ms β–ˆβ–ˆβ–ˆβ–ˆ
    20.0K     322ms    319ms    329ms β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ
    24.0K     459ms    457ms    460ms β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ
    28.0K    2098ms   2085ms   2318ms β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ
    32.0K    4557ms   4201ms   4875ms β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ

## synthetic-complex
  Variables, functions, :for, :foreach, :if β€” heavy scripting
  Syntax profile: scripting

     Size   Median     Min     Max Curve
  ─────── ──────── ─────── ─────── ──────────────────────────────
     0.1K       5ms      4ms      5ms β–ˆ
     0.3K       7ms      6ms      9ms β–ˆ
     0.5K      14ms     12ms     14ms β–ˆ
     1.0K      24ms     22ms     27ms β–ˆ
     2.0K      46ms     45ms     47ms β–ˆ
     3.0K      72ms     69ms     80ms β–ˆ
     4.0K     110ms     98ms    139ms β–ˆβ–ˆ
     6.0K     167ms    166ms    191ms β–ˆβ–ˆβ–ˆ
     8.0K     242ms    233ms    248ms β–ˆβ–ˆβ–ˆβ–ˆβ–ˆ
    10.0K     335ms    326ms    344ms β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ
    12.0K     528ms    514ms    534ms β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ
    16.0K     755ms    745ms    796ms β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ
    20.0K    1027ms    991ms   1038ms β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ
    24.0K    1398ms   1369ms   1448ms β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ
    28.0K    5158ms   5120ms   5179ms β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ
    32.0K    6022ms   5665ms   6025ms β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ

## eworm/global-functions.rsc
  Eworm global functions library β€” dense scripting, many globals
  Syntax profile: scripting

     Size   Median     Min     Max Curve
  ─────── ──────── ─────── ─────── ──────────────────────────────
     0.1K       3ms      3ms      3ms β–ˆ
     0.3K       5ms      4ms      6ms β–ˆ
     0.5K      10ms     10ms     12ms β–ˆ
     1.0K      22ms     21ms     25ms β–ˆ
     2.0K      53ms     49ms     57ms β–ˆ
     3.0K      81ms     77ms     87ms β–ˆβ–ˆ
     4.0K     134ms    117ms    136ms β–ˆβ–ˆβ–ˆ
     6.0K     197ms    195ms    201ms β–ˆβ–ˆβ–ˆβ–ˆ
     8.0K     287ms    280ms    309ms β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ
    10.0K     392ms    390ms    395ms β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ
    12.0K     458ms    412ms    485ms β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ
    16.0K     627ms    618ms    692ms β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ
    20.0K     943ms    941ms    980ms β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ
    24.0K    1198ms   1197ms   1252ms β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ
    28.0K    4053ms   3878ms   4239ms β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ
    32.0K    3721ms   3617ms   3972ms β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ

## edge-cases/oversize-32k.rsc
  Auto-generated repetitive address-list commands, >32KB
  Syntax profile: single-path

     Size   Median     Min     Max Curve
  ─────── ──────── ─────── ─────── ──────────────────────────────
     0.1K       2ms      2ms      2ms β–ˆ
     0.3K       4ms      3ms      4ms β–ˆ
     0.5K       6ms      5ms      6ms β–ˆ
     1.0K      10ms     10ms     11ms β–ˆ
     2.0K      22ms     21ms     23ms β–ˆ
     3.0K      34ms     32ms     38ms β–ˆ
     4.0K      47ms     46ms     49ms β–ˆ
     6.0K      77ms     75ms     84ms β–ˆβ–ˆ
     8.0K     107ms    106ms    112ms β–ˆβ–ˆ
    10.0K     146ms    144ms    149ms β–ˆβ–ˆβ–ˆ
    12.0K     200ms    197ms    204ms β–ˆβ–ˆβ–ˆβ–ˆ
    16.0K     280ms    276ms    300ms β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ
    20.0K     440ms    426ms    461ms β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ
    24.0K     571ms    564ms    582ms β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ
    28.0K    2163ms   2094ms   2186ms β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ
    32.0K    2441ms   2384ms   2489ms β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ

## complex/piano.rsc
  Piano player script β€” complex :beep, arrays, timing
  Syntax profile: scripting

     Size   Median     Min     Max Curve
  ─────── ──────── ─────── ─────── ──────────────────────────────
     0.1K       2ms      2ms      2ms β–ˆ
     0.3K       3ms      3ms      4ms β–ˆ
     0.5K       5ms      5ms      5ms β–ˆ
     1.0K      10ms      9ms     11ms β–ˆ
     2.0K      24ms     23ms     24ms β–ˆ
     3.0K      38ms     38ms     38ms β–ˆ
     4.0K      53ms     51ms     53ms β–ˆ
     6.0K      86ms     85ms     99ms β–ˆβ–ˆ
     8.0K     120ms    117ms    120ms β–ˆβ–ˆ
    10.0K     160ms    159ms    162ms β–ˆβ–ˆβ–ˆ

## Key Comparisons

  At 1KB:
    synthetic-comments                  6ms  (comments)
    synthetic-commands                  8ms  (single-path)
    synthetic-mixed-paths               8ms  (multi-path)
    synthetic-complex                  24ms  (scripting)
    eworm/global-functions.rsc         22ms  (scripting)
    edge-cases/oversize-32k.rsc        10ms  (single-path)
    complex/piano.rsc                  10ms  (scripting)

  At 4KB:
    synthetic-comments                 21ms  (comments)
    synthetic-commands                 33ms  (single-path)
    synthetic-mixed-paths              33ms  (multi-path)
    synthetic-complex                 110ms  (scripting)
    eworm/global-functions.rsc        134ms  (scripting)
    edge-cases/oversize-32k.rsc        47ms  (single-path)
    complex/piano.rsc                  53ms  (scripting)

  At 16KB:
    synthetic-comments                162ms  (comments)
    synthetic-commands                226ms  (single-path)
    synthetic-mixed-paths             222ms  (multi-path)
    synthetic-complex                 755ms  (scripting)
    eworm/global-functions.rsc        627ms  (scripting)
    edge-cases/oversize-32k.rsc       280ms  (single-path)

  At 32KB:
    synthetic-comments               2199ms  (comments)
    synthetic-commands               2351ms  (single-path)
    synthetic-mixed-paths            4557ms  (multi-path)
    synthetic-complex                6022ms  (scripting)
    eworm/global-functions.rsc       3721ms  (scripting)
    edge-cases/oversize-32k.rsc      2441ms  (single-path)

────────────────────────────────────────────────────────────────────────────────

CoPilot summary:

All 7 profiles are created and complete. The key findings:

  1. ALL syntax types scale quadratically β€” even pure # comment lines. This is inherent to the RouterOS API, not syntax-specific.
  2. Sharp inflection at ~28KB β€” every profile shows a dramatic 3.7–4.6Γ— jump between 24KB and 28KB. This is almost certainly a RouterOS internal buffer boundary.
  3. Syntax IS a multiplier, but not the root cause. At 32KB:
  • Comments: 2.2s (baseline)
  • Simple commands: 2.4s (1.1Γ—)
  • Mixed paths: 4.6s (2.1Γ—)
  • Complex scripting: 6.0s (2.7Γ—)
  1. Your Amm0's theory was half right β€” complex syntax (variables, functions, path lookups) costs ~3Γ— more than comments at the same size. But the quadratic base cost from pure length dominates. Both factors compound.

Oh, perhaps I should consider splitting my global-functions... :face_with_peeking_eye:

I suspect it does go through the same tokenizer... but likely only once in runtime since it does convert global functions into code it :parse format in /environment. The LSP has to do this every few seconds when typing... so it's a bigger issue there.

@eworm, I put an ATTRIBUTION.md with your scripts, and cross-ref in contributing docs - let me know if you have any problem with my code using them (not the expert in licensing, so my project is MIT since VSCode Extensions requires MIT or MIT-like.

The same is true for @rextended whose scripts are also part of the test corpus, his licensing rules I believe a "Thanks" and link, which was also cross-ref

Both sets of scripts are in GitHub for the units test (along with my posts). None of the scripts are published with the VSCode Extension. But either @eworm or @rextended ... let me know if you have any issue, and I'll remove them. Basically @eworm collection has some length+complexity and @rextended had volume+complexity.

The combo of scripts turned up that I was missing some "tokens":

  • variable-auto β€” in 167 files. This is significant β€” it's the 13th most common token type and completely unhandled. Likely auto-created variables like $0, $1 from regex captures or similar.
  • obj-dynamic β€” in 4 files. Like obj-inactive but for dynamic entries.
  • obj-disabled β€” in 2 files. Like obj-inactive but for disabled entries specifically.

Now these only affect coloring since they are no "error tokens". I believe RouterOS does not have text coloring for them AFAIK, still need to check.

My scripts are provided on the condition that:

  1. If there's an error, it's reported to me;
  2. Those who use them don't take credit for it, or rather, don't write who made it if they don't like me.
    :squinting_face_with_tongue:

LOL. I don't know yet. I know only a few dozen had an error tokens. But since scripts were scraped, or may be partial examples, hard to know if there real (could be my LSP, could be extract, partials scripts, etc)... I don't have a detailed break down of that yet since I'm still building tests. That "large scripts" are very slow in VSCode was the more pressing problem. But I do plan to a "lint report" to build to know. But LSP, it's that the count of tokens don't change over time, and we want errors in test data. If you have a file/ZIP with your scripts, feel to post it (or email tikoci.repos at the common domain for google mail), and I'll add those.

Looping over them is pretty easy as unit tests bring up a CHR already, so some check that essentially tell you that if pasted into CLI would result in the red ANSI is possible. My problem is while ~95% sure my LSP find same things, I'm missing a test that use SSH in test run to read back the ANSI an actual RouterOS terminal to confirm that. Whether scripts work for the purpose designed... that be much hard. No errors (or warns on undeclared variables) is easier.

There is a "manifest" for the sources here: lsp-routeros-ts/test-data/forum/rextended/manifest.json at main Β· tikoci/lsp-routeros-ts Β· GitHub with the parent directory having the snippets.

Well, @rextended's "manifest.json" is >64K, so :deserialize did NOT work. There is now a indice.json that has a bit less data, but you can run them yourself now. Essentially this the dumb down version of the VSCode/generic RouterOS LSP (easier since LSP protocol uses Unicode (e.g. ln#, pos#), so UTF-8 is "one character" to an LSP since that's how Unicode editor see it... but ~3 chars to RouterOS, so real LSP has track all that...):

$rsclint script=<script-text-as-string>

Outputs: array
length - length of script in bytes
tokens - inspect's "request=highlight" results as array
error - location as index, actual "errors" tokens found
obj-inactive - location as index, "bad code"


:global rsclint do={
    :local tokens ([/console/inspect request=highlight input=$script as-value]->"highlight")
    :local report {
        "length"=[:len $script];
        "tokens"=$tokens;
        "errors"=[:toarray ""];
        "obj-inactive"=[:toarray ""];
    }
    :for pos from=0 to=($report->"length") do={
        :if (($tokens->$pos) = "error") do={
            :set ($report->"errors") (($report->"errors"),$pos)
        }
        :if (($tokens->$pos) = "obj-inactive") do={
            :set ($report->"obj-inactive") (($report->"obj-inactive"),$pos)
        }
    }
    :return $report
}

$rexlint - uses $rsclint above against GitHub with @rextended's scripts

Output when run is below, showing files with errors. I have full faith that @rextended can add his own flair, but data has the name/url/etc... just be a lot of output for forum. The was human written... the LLM are better at the TypeScript, scraping discourse, and ANSI graphics than me I assure you.

:global rexlint do={
    :global rsclint
    :local manifestUrl "https://raw.githubusercontent.com/tikoci/lsp-routeros-ts/refs/heads/main/test-data/forum/rextended/indice.json"
    :local getGitHubUrlForScript do={ 
        :return "https://raw.githubusercontent.com/tikoci/lsp-routeros-ts/refs/heads/main/test-data/forum/rextended/$fileName" 
    }
    :local manifestJsonData [/tool/fetch url=$manifestUrl output=user as-value]
    :local manifestArray [:deserialize from=json ($manifestJsonData->"data")] 
    :foreach scriptManifest in=$manifestArray do={
        :local scriptdata [/tool/fetch url=[$getGitHubUrlForScript fileName=($scriptManifest->"fileName")] output=user as-value]
        :local lintout [$rsclint script=($scriptdata->"data")] 
        :put " $($scriptManifest->"fileName") \t sym# $[:len ($lintout->"tokens")] \t err# $[:len ($lintout->"errors")] \t bad# $[:len ($lintout->"obj-inactive")]"
    }
}

$rexlint

when run:

[admin@CHR] > :global rsclint do={
{...     :local tokens ([/console/inspect request=highlight input=$script as-value]->"highlight")
{...     :local report {
{{...         "length"=[:len $script];
{{...         "tokens"=$tokens;
{{...         "errors"=[:toarray ""];
{{...         "obj-inactive"=[:toarray ""];
{{...     }
{...     :for pos from=0 to=($report->"length") do={
{{...         :if (($tokens->$pos) = "error") do={
{{{...             :set ($report->"errors") (($report->"errors"),$pos)
{{{...         }
{{...         :if (($tokens->$pos) = "obj-inactive") do={
{{{...             :set ($report->"obj-inactive") (($report->"obj-inactive"),$pos)
{{{...         }
{{...     }
{...     :return $report
{... }
[admin@CHR] > 
[admin@CHR] > :global rexlint do={
{...     :global rsclint
{...     :local manifestUrl "https://raw.githubusercontent.com/tikoci/lsp-routeros-ts/refs/heads/main/test-data/forum/rextended/indice.json"
{...     :local getGitHubUrlForScript do={ 
{{...         :return "https://raw.githubusercontent.com/tikoci/lsp-routeros-ts/refs/heads/main/test-data/forum/rextended/$fileName" 
{{...     }
{...     :local manifestJsonData [/tool/fetch url=$manifestUrl output=user as-value]
{...     :local manifestArray [:deserialize from=json ($manifestJsonData->"data")] 
{...     :foreach scriptManifest in=$manifestArray do={
{{...         :local scriptdata [/tool/fetch url=[$getGitHubUrlForScript fileName=($scriptManifest->"fileName")] output=user as-value]
{{...         :local lintout [$rsclint script=($scriptdata->"data")] 
{{...         :put " $($scriptManifest->"fileName") \t sym# $[:len ($lintout->"tokens")] \t err# $[:len ($lintout->"errors")] \t bad# $[:len ($lintout->"obj-inactive")]"
{{...     }
{... }
[admin@CHR] > 
[admin@CHR] > $rexlint
 topic-151033-post-0010-snippet-01.rsc   sym# 190        err# 0          bad# 0
 topic-151033-post-0015-snippet-01.rsc   sym# 1008       err# 0          bad# 0
 topic-151033-post-0015-snippet-02.rsc   sym# 1021       err# 0          bad# 0
 topic-151033-post-0015-snippet-03.rsc   sym# 161        err# 0          bad# 0
 topic-151033-post-0015-snippet-04.rsc   sym# 165        err# 0          bad# 0
 topic-151033-post-0015-snippet-05.rsc   sym# 172        err# 0          bad# 0
 topic-151033-post-0015-snippet-06.rsc   sym# 165        err# 0          bad# 0
 topic-151033-post-0017-snippet-01.rsc   sym# 653        err# 0          bad# 0
 topic-151033-post-0017-snippet-02.rsc   sym# 819        err# 0          bad# 0
 topic-151033-post-0018-snippet-01.rsc   sym# 451        err# 0          bad# 0
 topic-151033-post-0018-snippet-02.rsc   sym# 840        err# 0          bad# 0
 topic-151033-post-0018-snippet-03.rsc   sym# 492        err# 0          bad# 0
 topic-151033-post-0018-snippet-04.rsc   sym# 1207       err# 0          bad# 0
 topic-151033-post-0019-snippet-01.rsc   sym# 173        err# 0          bad# 0
 topic-151033-post-0019-snippet-02.rsc   sym# 333        err# 0          bad# 0
 topic-151033-post-0020-snippet-01.rsc   sym# 3892       err# 0          bad# 0
 topic-151033-post-0020-snippet-02.rsc   sym# 3891       err# 0          bad# 0
 topic-151033-post-0021-snippet-01.rsc   sym# 494        err# 0          bad# 0
 topic-151033-post-0021-snippet-02.rsc   sym# 658        err# 0          bad# 0
 topic-151033-post-0034-snippet-01.rsc   sym# 3806       err# 0          bad# 0
 topic-151033-post-0034-snippet-02.rsc   sym# 3306       err# 0          bad# 0
 topic-151033-post-0048-snippet-01.rsc   sym# 3524       err# 0          bad# 0
 topic-151033-post-0048-snippet-02.rsc   sym# 362        err# 1          bad# 9
 topic-151033-post-0049-snippet-01.rsc   sym# 4047       err# 0          bad# 0
 topic-151033-post-0049-snippet-02.rsc   sym# 226        err# 1          bad# 0
 topic-151033-post-0056-snippet-01.rsc   sym# 3265       err# 0          bad# 0
 topic-151033-post-0056-snippet-02.rsc   sym# 277        err# 1          bad# 0
 topic-151033-post-0057-snippet-01.rsc   sym# 3838       err# 0          bad# 0
 topic-151033-post-0057-snippet-02.rsc   sym# 648        err# 1          bad# 9
 topic-151033-post-0058-snippet-01.rsc   sym# 181        err# 1          bad# 0
 topic-151033-post-0070-snippet-01.rsc   sym# 3903       err# 0          bad# 0
 topic-151033-post-0070-snippet-02.rsc   sym# 3895       err# 0          bad# 0
 topic-151033-post-0071-snippet-01.rsc   sym# 3800       err# 0          bad# 0
 topic-151033-post-0071-snippet-02.rsc   sym# 243        err# 1          bad# 0
 topic-151033-post-0072-snippet-01.rsc   sym# 3296       err# 0          bad# 0
 topic-151033-post-0072-snippet-02.rsc   sym# 182        err# 0          bad# 0
 topic-151033-post-0072-snippet-03.rsc   sym# 614        err# 1          bad# 0
 topic-103825-post-0017-snippet-01.rsc   sym# 552        err# 0          bad# 0
 topic-103825-post-0017-snippet-02.rsc   sym# 717        err# 1          bad# 11
 topic-103825-post-0021-snippet-01.rsc   sym# 347        err# 0          bad# 0
 topic-103825-post-0024-snippet-01.rsc   sym# 188        err# 0          bad# 0
 topic-132775-post-0017-snippet-01.rsc   sym# 2306       err# 0          bad# 0
 topic-132775-post-0017-snippet-02.rsc   sym# 265        err# 0          bad# 0
 topic-63496-post-0005-snippet-01.rsc    sym# 1475       err# 0          bad# 0
 topic-121369-post-0013-snippet-01.rsc   sym# 1649       err# 0          bad# 0
 topic-68576-post-0014-snippet-01.rsc    sym# 1117       err# 0          bad# 0
 topic-68576-post-0014-snippet-02.rsc    sym# 1195       err# 0          bad# 0
 topic-68576-post-0018-snippet-01.rsc    sym# 1153       err# 0          bad# 0
 topic-68576-post-0018-snippet-02.rsc    sym# 186        err# 0          bad# 0
 topic-68576-post-0019-snippet-01.rsc    sym# 1425       err# 0          bad# 0
 topic-68576-post-0024-snippet-01.rsc    sym# 1883       err# 0          bad# 0
 topic-68576-post-0033-snippet-01.rsc    sym# 169        err# 0          bad# 0
 topic-68576-post-0039-snippet-01.rsc    sym# 187        err# 0          bad# 0
 topic-143071-post-0014-snippet-01.rsc   sym# 235        err# 1          bad# 8
 topic-143071-post-0018-snippet-01.rsc   sym# 845        err# 0          bad# 0
 topic-143071-post-0018-snippet-02.rsc   sym# 438        err# 0          bad# 0
 topic-143071-post-0023-snippet-01.rsc   sym# 3602       err# 0          bad# 0
 topic-143071-post-0025-snippet-01.rsc   sym# 2936       err# 0          bad# 0
 topic-143071-post-0029-snippet-01.rsc   sym# 1463       err# 0          bad# 0
 topic-143071-post-0033-snippet-01.rsc   sym# 502        err# 0          bad# 0
 topic-143071-post-0052-snippet-01.rsc   sym# 315        err# 0          bad# 0
 topic-151020-post-0001-snippet-01.rsc   sym# 350        err# 0          bad# 5
 topic-151020-post-0001-snippet-02.rsc   sym# 1006       err# 0          bad# 0
 topic-115644-post-0004-snippet-01.rsc   sym# 879        err# 0          bad# 0
 topic-97913-post-0009-snippet-01.rsc    sym# 277        err# 1          bad# 9
 topic-97913-post-0011-snippet-01.rsc    sym# 262        err# 0          bad# 0
 topic-97913-post-0011-snippet-02.rsc    sym# 369        err# 0          bad# 0
 topic-97913-post-0014-snippet-01.rsc    sym# 180        err# 0          bad# 3
 topic-133640-post-0087-snippet-01.rsc   sym# 187        err# 0          bad# 0
 topic-133640-post-0088-snippet-01.rsc   sym# 212        err# 1          bad# 3
 topic-133640-post-0088-snippet-02.rsc   sym# 241        err# 1          bad# 6
 topic-133640-post-0088-snippet-03.rsc   sym# 527        err# 1          bad# 12
 topic-133640-post-0114-snippet-01.rsc   sym# 382        err# 1          bad# 0
 topic-133640-post-0114-snippet-02.rsc   sym# 235        err# 1          bad# 0
 topic-133640-post-0116-snippet-01.rsc   sym# 673        err# 1          bad# 9
 topic-133640-post-0120-snippet-01.rsc   sym# 970        err# 0          bad# 0
 topic-133640-post-0158-snippet-01.rsc   sym# 2762       err# 0          bad# 0
 topic-133640-post-0158-snippet-02.rsc   sym# 565        err# 1          bad# 3
 topic-133640-post-0158-snippet-03.rsc   sym# 752        err# 0          bad# 0
 topic-133640-post-0182-snippet-01.rsc   sym# 180        err# 1          bad# 9
 topic-133640-post-0197-snippet-01.rsc   sym# 197        err# 0          bad# 0
 topic-149116-post-0021-snippet-01.rsc   sym# 329        err# 0          bad# 6
 topic-149116-post-0021-snippet-02.rsc   sym# 273        err# 1          bad# 1
 topic-149116-post-0021-snippet-03.rsc   sym# 384        err# 1          bad# 9
 topic-149116-post-0027-snippet-01.rsc   sym# 260        err# 1          bad# 0
 topic-52654-post-0005-snippet-01.rsc    sym# 198        err# 0          bad# 0
 topic-52654-post-0005-snippet-02.rsc    sym# 494        err# 0          bad# 0
 topic-52654-post-0005-snippet-03.rsc    sym# 169        err# 0          bad# 5
 topic-52654-post-0005-snippet-04.rsc    sym# 311        err# 0          bad# 0
 topic-52654-post-0005-snippet-05.rsc    sym# 578        err# 0          bad# 0
 topic-52654-post-0006-snippet-01.rsc    sym# 619        err# 0          bad# 0
 topic-52654-post-0006-snippet-02.rsc    sym# 399        err# 0          bad# 0
 topic-150559-post-0002-snippet-01.rsc   sym# 538        err# 0          bad# 0
 topic-150559-post-0002-snippet-02.rsc   sym# 900        err# 0          bad# 0
 topic-150559-post-0007-snippet-01.rsc   sym# 226        err# 0          bad# 0
 topic-150195-post-0002-snippet-01.rsc   sym# 638        err# 0          bad# 0
 topic-150195-post-0008-snippet-01.rsc   sym# 718        err# 0          bad# 0
 topic-150195-post-0012-snippet-01.rsc   sym# 268        err# 0          bad# 0
 topic-150195-post-0012-snippet-02.rsc   sym# 268        err# 0          bad# 0
 topic-150516-post-0008-snippet-01.rsc   sym# 211        err# 0          bad# 0
 topic-150516-post-0008-snippet-02.rsc   sym# 211        err# 0          bad# 0
 topic-150516-post-0008-snippet-03.rsc   sym# 502        err# 0          bad# 0
 topic-150516-post-0025-snippet-01.rsc   sym# 234        err# 0          bad# 0
 topic-150516-post-0025-snippet-02.rsc   sym# 309        err# 0          bad# 0
 topic-150516-post-0027-snippet-01.rsc   sym# 598        err# 0          bad# 0
 topic-150066-post-0008-snippet-01.rsc   sym# 471        err# 0          bad# 0
 topic-150066-post-0010-snippet-01.rsc   sym# 291        err# 1          bad# 0
 topic-150050-post-0002-snippet-01.rsc   sym# 254        err# 0          bad# 0
 topic-150050-post-0002-snippet-02.rsc   sym# 357        err# 0          bad# 0
 topic-150050-post-0002-snippet-03.rsc   sym# 248        err# 0          bad# 0
 topic-150050-post-0004-snippet-01.rsc   sym# 452        err# 0          bad# 0
 topic-110950-post-0010-snippet-01.rsc   sym# 493        err# 0          bad# 0
 topic-110950-post-0028-snippet-01.rsc   sym# 4049       err# 0          bad# 0
 topic-79536-post-0006-snippet-01.rsc    sym# 348        err# 0          bad# 0
 topic-104992-post-0083-snippet-01.rsc   sym# 210        err# 0          bad# 16
 topic-149267-post-0001-snippet-01.rsc   sym# 431        err# 0          bad# 0
 topic-149267-post-0001-snippet-02.rsc   sym# 422        err# 0          bad# 0
 topic-148996-post-0004-snippet-01.rsc   sym# 2044       err# 0          bad# 0
 topic-148996-post-0004-snippet-02.rsc   sym# 4109       err# 0          bad# 0
 topic-148996-post-0004-snippet-03.rsc   sym# 2203       err# 0          bad# 0
 topic-148996-post-0004-snippet-04.rsc   sym# 4267       err# 0          bad# 0
 topic-148996-post-0036-snippet-01.rsc   sym# 814        err# 1          bad# 4
 topic-113429-post-0004-snippet-01.rsc   sym# 1139       err# 0          bad# 0
 topic-151793-post-0002-snippet-01.rsc   sym# 223        err# 1          bad# 0
 topic-151793-post-0002-snippet-02.rsc   sym# 197        err# 1          bad# 0
 topic-151793-post-0002-snippet-03.rsc   sym# 302        err# 0          bad# 0
 topic-151793-post-0009-snippet-01.rsc   sym# 378        err# 0          bad# 0
 topic-151793-post-0020-snippet-01.rsc   sym# 187        err# 1          bad# 0
 topic-151723-post-0005-snippet-01.rsc   sym# 608        err# 1          bad# 0
 topic-151723-post-0007-snippet-01.rsc   sym# 948        err# 0          bad# 0
 topic-151723-post-0007-snippet-02.rsc   sym# 565        err# 1          bad# 0
 topic-151870-post-0002-snippet-01.rsc   sym# 1398       err# 0          bad# 0
 topic-151870-post-0010-snippet-01.rsc   sym# 737        err# 0          bad# 0
 topic-77321-post-0015-snippet-01.rsc    sym# 494        err# 0          bad# 0
 topic-151933-post-0002-snippet-01.rsc   sym# 217        err# 0          bad# 0
 topic-151933-post-0005-snippet-01.rsc   sym# 1722       err# 0          bad# 0
 topic-151933-post-0005-snippet-02.rsc   sym# 232        err# 1          bad# 4
 topic-151933-post-0005-snippet-03.rsc   sym# 225        err# 0          bad# 0
 topic-145326-post-0006-snippet-01.rsc   sym# 687        err# 0          bad# 0
 topic-145326-post-0006-snippet-02.rsc   sym# 1563       err# 0          bad# 0
 topic-145326-post-0017-snippet-01.rsc   sym# 265        err# 0          bad# 0
 topic-145326-post-0017-snippet-02.rsc   sym# 207        err# 0          bad# 0
 topic-145326-post-0043-snippet-01.rsc   sym# 159        err# 0          bad# 0
 topic-145326-post-0056-snippet-01.rsc   sym# 583        err# 1          bad# 0
 topic-149189-post-0007-snippet-01.rsc   sym# 1479       err# 0          bad# 6
 topic-149189-post-0007-snippet-02.rsc   sym# 373        err# 1          bad# 12
 topic-149189-post-0007-snippet-03.rsc   sym# 368        err# 1          bad# 0
 topic-149189-post-0007-snippet-04.rsc   sym# 345        err# 1          bad# 0
 topic-149189-post-0007-snippet-05.rsc   sym# 534        err# 1          bad# 0
 topic-157724-post-0005-snippet-01.rsc   sym# 5180       err# 0          bad# 0
 topic-157724-post-0012-snippet-01.rsc   sym# 5266       err# 0          bad# 0
 topic-157724-post-0020-snippet-01.rsc   sym# 149        err# 0          bad# 0
 topic-157724-post-0036-snippet-01.rsc   sym# 1561       err# 0          bad# 0
 topic-157724-post-0037-snippet-01.rsc   sym# 1053       err# 0          bad# 0
 topic-157724-post-0037-snippet-02.rsc   sym# 172        err# 0          bad# 0
 topic-157724-post-0037-snippet-03.rsc   sym# 190        err# 0          bad# 0
 topic-157724-post-0040-snippet-01.rsc   sym# 265        err# 0          bad# 0
 topic-158109-post-0005-snippet-01.rsc   sym# 795        err# 0          bad# 0
 topic-158109-post-0005-snippet-02.rsc   sym# 1258       err# 1          bad# 3
 topic-158109-post-0015-snippet-01.rsc   sym# 392        err# 0          bad# 0
 topic-158109-post-0016-snippet-01.rsc   sym# 1079       err# 0          bad# 0
 topic-158109-post-0016-snippet-02.rsc   sym# 583        err# 1          bad# 3
 topic-158216-post-0004-snippet-01.rsc   sym# 557        err# 0          bad# 0
 topic-158216-post-0005-snippet-01.rsc   sym# 515        err# 0          bad# 0
 topic-158216-post-0005-snippet-02.rsc   sym# 622        err# 0          bad# 0
 topic-158216-post-0013-snippet-01.rsc   sym# 867        err# 0          bad# 0
 topic-158216-post-0013-snippet-02.rsc   sym# 400        err# 0          bad# 0
 topic-74610-post-0002-snippet-01.rsc    sym# 369        err# 0          bad# 0
 topic-74610-post-0004-snippet-01.rsc    sym# 224        err# 0          bad# 0
 topic-75450-post-0043-snippet-01.rsc    sym# 172        err# 0          bad# 12
 topic-75450-post-0043-snippet-02.rsc    sym# 134        err# 1          bad# 5
 topic-75450-post-0110-snippet-01.rsc    sym# 173        err# 0          bad# 12
 topic-75450-post-0110-snippet-02.rsc    sym# 135        err# 1          bad# 5
 topic-75450-post-0140-snippet-01.rsc    sym# 4448       err# 1          bad# 5
 topic-160011-post-0004-snippet-01.rsc   sym# 1355       err# 0          bad# 0
 topic-160011-post-0005-snippet-01.rsc   sym# 1330       err# 0          bad# 0
 topic-160011-post-0006-snippet-01.rsc   sym# 862        err# 0          bad# 0
 topic-160011-post-0007-snippet-01.rsc   sym# 2096       err# 0          bad# 0
 topic-161897-post-0005-snippet-01.rsc   sym# 354        err# 1          bad# 1
 topic-161897-post-0006-snippet-01.rsc   sym# 1205       err# 0          bad# 0
 topic-5183-post-0010-snippet-01.rsc     sym# 973        err# 0          bad# 0
 topic-5183-post-0010-snippet-02.rsc     sym# 3036       err# 0          bad# 0
 topic-5183-post-0017-snippet-01.rsc     sym# 309        err# 0          bad# 0
 topic-5183-post-0019-snippet-01.rsc     sym# 3206       err# 0          bad# 0
 topic-5183-post-0019-snippet-02.rsc     sym# 230        err# 0          bad# 0
 topic-5183-post-0022-snippet-01.rsc     sym# 3943       err# 0          bad# 0
 topic-5183-post-0022-snippet-02.rsc     sym# 354        err# 0          bad# 0
 topic-5183-post-0023-snippet-01.rsc     sym# 883        err# 0          bad# 0
 topic-162755-post-0002-snippet-01.rsc   sym# 183        err# 1          bad# 5
 topic-162755-post-0006-snippet-01.rsc   sym# 1404       err# 1          bad# 5
 topic-162848-post-0002-snippet-01.rsc   sym# 381        err# 0          bad# 0
 topic-162848-post-0004-snippet-01.rsc   sym# 2605       err# 0          bad# 0
 topic-163033-post-0003-snippet-01.rsc   sym# 771        err# 0          bad# 0
 topic-163033-post-0006-snippet-01.rsc   sym# 401        err# 0          bad# 0
 topic-163033-post-0015-snippet-01.rsc   sym# 1677       err# 0          bad# 0
 topic-163033-post-0018-snippet-01.rsc   sym# 195        err# 1          bad# 0
 topic-163033-post-0021-snippet-01.rsc   sym# 1704       err# 0          bad# 0
 topic-163033-post-0038-snippet-01.rsc   sym# 251        err# 0          bad# 0
 topic-163033-post-0038-snippet-02.rsc   sym# 189        err# 0          bad# 0
 topic-163033-post-0038-snippet-03.rsc   sym# 223        err# 0          bad# 0
 topic-163033-post-0042-snippet-01.rsc   sym# 490        err# 0          bad# 0
 topic-163033-post-0042-snippet-02.rsc   sym# 480        err# 0          bad# 0
 topic-163033-post-0046-snippet-01.rsc   sym# 275        err# 0          bad# 0
 topic-163033-post-0046-snippet-02.rsc   sym# 216        err# 0          bad# 0
 topic-163067-post-0009-snippet-01.rsc   sym# 290        err# 0          bad# 0
 topic-163067-post-0011-snippet-01.rsc   sym# 801        err# 1          bad# 0
 topic-163488-post-0005-snippet-01.rsc   sym# 187        err# 1          bad# 1
 topic-163850-post-0003-snippet-01.rsc   sym# 650        err# 0          bad# 0
 topic-163850-post-0007-snippet-01.rsc   sym# 262        err# 0          bad# 0
 topic-163718-post-0012-snippet-01.rsc   sym# 3569       err# 0          bad# 0
 topic-164329-post-0017-snippet-01.rsc   sym# 851        err# 1          bad# 9
 topic-164329-post-0018-snippet-01.rsc   sym# 2776       err# 0          bad# 0
 topic-164329-post-0025-snippet-01.rsc   sym# 190        err# 1          bad# 1
 topic-164329-post-0025-snippet-02.rsc   sym# 183        err# 1          bad# 1
 topic-164329-post-0025-snippet-03.rsc   sym# 177        err# 1          bad# 1
 topic-164329-post-0026-snippet-01.rsc   sym# 4391       err# 0          bad# 0
 topic-164329-post-0026-snippet-02.rsc   sym# 4371       err# 0          bad# 0
 topic-164329-post-0034-snippet-01.rsc   sym# 212        err# 0          bad# 0
 topic-164329-post-0035-snippet-01.rsc   sym# 197        err# 0          bad# 0
 topic-142687-post-0012-snippet-01.rsc   sym# 144        err# 0          bad# 0
 topic-142687-post-0013-snippet-01.rsc   sym# 178        err# 0          bad# 0
 topic-142687-post-0013-snippet-02.rsc   sym# 184        err# 0          bad# 0
 topic-142687-post-0013-snippet-03.rsc   sym# 211        err# 0          bad# 0
 topic-164889-post-0003-snippet-01.rsc   sym# 2155       err# 0          bad# 0
 topic-164889-post-0004-snippet-01.rsc   sym# 3758       err# 0          bad# 0
 topic-164889-post-0005-snippet-01.rsc   sym# 1140       err# 0          bad# 0
 topic-164889-post-0006-snippet-01.rsc   sym# 1279       err# 0          bad# 0
 topic-164889-post-0007-snippet-01.rsc   sym# 2620       err# 0          bad# 0
 topic-164889-post-0007-snippet-02.rsc   sym# 416        err# 1          bad# 0
 topic-164889-post-0008-snippet-01.rsc   sym# 5087       err# 0          bad# 0
 topic-166898-post-0002-snippet-01.rsc   sym# 281        err# 1          bad# 0
 topic-166898-post-0022-snippet-01.rsc   sym# 6052       err# 0          bad# 0
 topic-166898-post-0024-snippet-01.rsc   sym# 6121       err# 0          bad# 0
 topic-166898-post-0052-snippet-01.rsc   sym# 778        err# 0          bad# 0
 topic-167618-post-0003-snippet-01.rsc   sym# 532        err# 0          bad# 0
 topic-132192-post-0021-snippet-01.rsc   sym# 189        err# 0          bad# 0
 topic-132192-post-0022-snippet-01.rsc   sym# 1069       err# 0          bad# 0
 topic-132192-post-0022-snippet-02.rsc   sym# 1163       err# 0          bad# 0
 topic-132192-post-0026-snippet-01.rsc   sym# 438        err# 0          bad# 0
 topic-132192-post-0026-snippet-02.rsc   sym# 313        err# 0          bad# 0
 topic-132192-post-0029-snippet-01.rsc   sym# 1440       err# 0          bad# 0
 topic-132192-post-0039-snippet-01.rsc   sym# 458        err# 1          bad# 4
 topic-132192-post-0041-snippet-01.rsc   sym# 197        err# 0          bad# 0
 topic-132192-post-0042-snippet-01.rsc   sym# 243        err# 1          bad# 0
 topic-156344-post-0004-snippet-01.rsc   sym# 233        err# 1          bad# 8
 topic-156344-post-0004-snippet-02.rsc   sym# 264        err# 0          bad# 0
 topic-156344-post-0004-snippet-03.rsc   sym# 268        err# 1          bad# 0
 topic-156344-post-0004-snippet-04.rsc   sym# 342        err# 0          bad# 0
 topic-156344-post-0004-snippet-05.rsc   sym# 625        err# 0          bad# 0
 topic-156344-post-0004-snippet-06.rsc   sym# 925        err# 0          bad# 0
 topic-156344-post-0004-snippet-07.rsc   sym# 1348       err# 0          bad# 0
 topic-166576-post-0012-snippet-01.rsc   sym# 231        err# 0          bad# 6
 topic-166576-post-0035-snippet-01.rsc   sym# 352        err# 0          bad# 0
 topic-166576-post-0035-snippet-02.rsc   sym# 356        err# 0          bad# 0
[admin@CHR] > 

Now the lousy syntax highlight looks like ass in forum. In fairness, some my colors get remapped by VSCode theme ATM (a bug), but this what I see with the LSP in VSCode:

The contributed scripts are quite helpful to have some set of scripts to run RouterOS syntax/structure experiments. As noted in thread, there are some things /console/inspect cannot tell us about RotuerOS script/CLI, like current path. This is tricky since there is a lot of RouterOS syntax that can do a implict cd, so while handing a few case may cover a lot of ground (e.g. config files have a simple structure, so determine the path is easier). I have thought before that the :parse output of a script/CLI/config be easier to parse out the path, but building a recursive decent parser take me some time. For the robots, that's child play, compared say configuring VLANs on a bridge. So have some tests, and report, on what I called the "parseIL Format" (e.g. the code/"(code)" data type for scripting denizens).

For the LSP, using :parse is quicker at getting a "valid" or "invalid" script results, even one larger than ~25K. So for larger scripts, "pre-parsing" them using :parse avoid the more expensive tokenizing.

But more importantly /console/inspect data can "enhanced" with the 'parseIL' Format, since the code form has to "unwind" more complex "routeros script form". e.g. another way to look at it is all of RouterOS script/cli/config is just "syntax sugar" over the code types... so the simpler parseIL form tells us a few things without developing our own RouterOS script parser. The TL;DR for the current research:

From Β§ 7 "LSP feature payoffs",

The features unlocked or clarified by this spike β€” to be filed as concrete
backlog items once the spike is closed:

  • Folding Ranges β€” straightforward off do=;(evl …) boundaries.

e.g. accurate "collapse" blocks in VSCode (the arrows up/down to hide/show parts of the code)

  • Document Symbols: functions, not just variables β€” detect
    (evl /localdo=…;name=$f) / (evl /globaldo=…;name=$F).

e.g. existing "highlight" tokens do not seperate functions from other variable, types, but the pattern above allow identifications of RouterOS functions... this should allow the "Go to defination" etc option to "jump" to where the function is declared.

  • "Show parseIL" command + hover supplement β€” verbatim IL display.

e.g. LSP currently shows the highlight token at a particular location so that if someone thought "LSP is wrong!", we'd know what token from RouterOS we got... so for a selection, seeing what the parseIL either directly or further parse for display as a "debug view" of the whole/part of script

  • Hover canonicalisation hints β€” display 200ms β†’ 00:00:00.200,
    yes β†’ true, :put β†’ /put.

e.g. some types like time are obvious in parseIL, like time, so we can likely know those, perhaps others - need more experiments here to see full scope.

  • :parse short-circuit before highlight β€” skip highlight when :parse
    reports a fatal syntax error; surface that error directly with line/col.

This is actually more useful to TikBook since parse is actually what should be shown, although still have to parse the string to find line/pos numbers since those are may not regularized accross versions, IDK

  • Definition / References (with caveat) β€” feasible via IL scope
    reconstruction; not free.

e.g. figure out path is possible, but as noted, require more parsing of the parseIL than others.

Underlying research report from these :parse/parseIL tests:

Side note to @rextended / @eworm - the project is now using SQLite to store various experiments done on the set of scripts, so eventually should be able to produce more meaningful "report" on your script libraries. In my ecosystem there are about 50 tracked version of RouterOS (7.9 ->), so what is valid in one version may not be valid in another. So storing test results as JSON files likely results in thousands of files. And, in case of eworm's collection, there is actually another dimension that test could use, but don't, is git for older versions - not sure we'll get there but the historical differences might hold different clues. At this point, I'm more trying to unwind the structure of RouterOS at this point, but results of tests will be in lsp-routeros-ts/test-data/corpus.sqlite at main Β· tikoci/lsp-routeros-ts Β· GitHub