Some Thoughts on Scripting
FVWM has always had a Domain Specific Language (DSL) to describe its config and behaviour. Plenty of other projects have something similar to a lesser or greater extent, even down to providing a configuration file is a DSL to some extent.
CoW is making use of tmux(1)'s command parsing as a basis for its own commands. This means different commands in Cow can have their own options, and indeed, this is what tmux(1) itself does.
tmux(1) takes this a stage further by also embedding a RPN (Reverse Polish Notation) means to provide scripting capabilities -- a very tight-knit DSL which anyone using tmux has used implicitly, or has used explicitly to some degree.
This is actually very powerful and allows for a lot of customisations, but it is still DSL one has to learn and use if one wishes to customise tmux to any great extent.
That said, this DSL hasn't stopped various frameworks and bindings from popping up, such as:
So it's possible to still compose commands specific to a DSL, even if it ends up being a compositor of string commands -- which in the case of tmux, it will be.
So DSLs, even if they don't expose a fine-grained API, can still allow a language to bind to various aspects of the DSL to to expose that in a programmatic way.
What Does CoW Need?
I'm a firm believer in DSLs that are easy to understand, and can be controlled via scripts (in any language). This might mean the scripting engine (programming language) providing its own APIs on top of the DSL, that's OK.
This is not a weakness but a feature, whereby it allows language-bindings to behave however they need to.
CoW needs a means of presenting concrete types which the user can iterate on. These include:
- Outputs
- Desks
- Windows
In doing so, such a type should therefore expose properties which can be used.
Iteration and Filtering
Being able to iterate over items and at the same time filter them based on conditions allows for target window(s) to be selected for a specific command.
Conditionals
if would be helpful for conditional branching.
Prototype
On the ta/dsl branch in the CoW repository, lives the code which has
prototype code to bring the above considerations to life.
Here's an example:
script {
for w in (windows where w.focused != true and w,iconified != true) {
winops -t '#{w.id}' -i
}
}
In this example the script keyword starts a scope of what will be a DSL
operation. Scripts do not have names, and a re-form, instead they can be
attached to bindings, or rules, for example.
w is the object which is exposed based on the filter:
(windows where w.focused != true and w,iconified != true). Here, windows
are filtered based on if they're not focused and not iconified.
Then, for all windows which match those conditions, the command winops -t '#{w.id}' -i is run, which iconfies those windows.
It's important to ensure that -t #{w.id} so that the correct window is used
by the winops command. Note as well that '#{...}' performs the expansion of
that window ID.
Here's another example:
rule -g -n top-tint -T on-layer-change script {
if self.layer >=8
set titlebar.active_colour 0xC04030
} else {
set titlebar.active_colour 0x303030
}
}
This script is bound to a rule for when a window changes layer, and will check if the window is in a layer greater than or equal to 8, and apply a titlebar colour of one colour, and a different colour for all other windows.
Note here, that self is referencing the window affected by the rule
command.
The following attributes are exposed...
Window Attributes
| Attrib | Meaning |
|---|---|
| id | The River ID of the window |
| app_id | The applicatioon ID of the window |
| title | The window title |
| desk | The desk the window is on |
| output | The output the window is on |
| layer | The layer the window is in |
Desk Attributes
| Attrib | Meaning |
|---|---|
| nr | The desk number |
| output | The output the desk is attached to |
| current | Whether the desk is the one being used |
| window_count | How many windows are on that desk |
Output Attributes
| Attrib | Meaning |
|---|---|
| name | The name of the output |
| number | The assigned number the output has |
| current | True if the output is the current one |
| current_desk | The number of the desk which is displayed on the output |
Implementation Details
Bison/Flex can define the grammar of the script command. Augmenting the
current parser to support this would be difficult.
Each grammar definition can convert to an AST which defines things such as:
- Blocks
- Let
- For
- If
- Not / And / Or
- Command
Scopes are therefore their own and create children.
Three structures during run:
struct dsl_env { name → dsl_value*, parent }; // lexical scope chain
struct dsl_eval_ctx { cmd_q*, dsl_error*, failed, implicit_target_id };
struct dsl_ast { root, arena };
Walk:
DSL_N_LETadds to current scope.DSL_N_FORsnapshots source, optionally filters via thewhereexpression evaluated per element, pushes a child scope binding the loop variable, recurses into the body.DSL_N_IFevaluates condition, recurses into the chosen branch.DSL_N_CMDbuilds argv (raw pieces verbatim, interp pieces evaluated and stringified), parses via cow's existingcmd_parse, wraps in acmd_list, andcmdq_appends onto the callingcmd_q.- Expressions evaluate recursively; comparisons require matching types (cross-type compare = eval error). Boolean ops short-circuit. Field access (
a.b.c) walks records; missing field is an eval error.
Errors carry a line:column from the originating AST node and abort the script (skip remaining statements) but don't abort the cmd_q.