Skip to content
opencode-agent[bot] edited this page May 10, 2026 · 2 revisions

Shell Syntax

JNode's syntax system parses, validates, and auto-completes command-line arguments using declarative Argument objects and Syntax trees, all before a Command's execute() is called.

Overview

The syntax system (org.jnode.shell.syntax) is a declarative argument-parsing framework for shell commands. Unlike conventional String[] args parsing, it uses Java objects to describe expected arguments, and a parser (MuParser) to validate and populate them. This provides:

  • Automatic validation — arguments are type-checked and constrained before execute() runs
  • Tab-completion — argument subtypes provide completions based on their domain (files, integers, etc.)
  • Help text — argument descriptions are used to generate usage messages
  • Multiple syntaxes — a command can support multiple argument patterns via SyntaxBundle

Key Components

Class / File Role
shell/src/shell/org/jnode/shell/syntax/Argument.java Base class for typed argument holders (FileArgument, StringArgument, FlagArgument, etc.)
shell/src/shell/org/jnode/shell/syntax/Syntax.java Abstract syntax tree node (SequenceSyntax, OptionSyntax, RepeatSyntax, etc.)
shell/src/shell/org/jnode/shell/syntax/SyntaxBundle.java Container holding one or more Syntax alternatives for a command alias
shell/src/shell/org/jnode/shell/syntax/SyntaxManager.java Service that maps command aliases to SyntaxBundles
shell/src/shell/org/jnode/shell/syntax/MuParser.java Backtracking parser that validates tokens against a MuSyntax tree
shell/src/shell/org/jnode/shell/syntax/MuSyntax.java Compiled "micro" syntax tree used by MuParser
shell/src/shell/org/jnode/shell/syntax/ArgumentBundle.java Container holding Argument instances for a command

How It Works

1. Declaring Arguments

Commands declare their arguments by instantiating Argument subclasses:

public class MyCommand extends AbstractCommand {
    private final StringArgument nameArg = new StringArgument("name", Argument.MANDATORY, "Your name");
    private final FileArgument fileArg = new FileArgument("file", Argument.EXISTING, "Input file");
    private final FlagArgument verboseFlag = new FlagArgument("verbose", Argument.OPTIONAL, "Enable verbose output");

    public MyCommand() {
        super("My command description");
        registerArguments(nameArg, fileArg, verboseFlag);
    }

    public void execute() throws Exception {
        // nameArg.getValue(), fileArg.getValues(), verboseFlag.isSet() are populated
        // validation already passed at this point
    }
}

2. Syntax Compilation

When a command is invoked, SyntaxManager looks up its SyntaxBundle. The Syntax.prepare() method compiles each Syntax into a MuSyntax tree:

  • SequenceSyntax — sequences of elements (argument, symbol, sub-syntax)
  • OptionSyntax — option patterns like -o, --output <arg>
  • RepeatSyntax — repetition (*, +)
  • AlternativesSyntax — multiple alternative patterns (e.g., cp src dest vs cp -r src dest)
  • GroupSyntax — group with label for labeling purposes

3. Parsing

MuParser performs backtracking parse against the MuSyntax:

  1. Reads tokens from CommandLine
  2. Matches symbols (MuSymbol) or arguments (MuArgument)
  3. For MuArgument, calls Argument.accept(token, flags) which validates and stores the value
  4. On failure, backtracks and tries alternatives
  5. On success, all Arguments in the bundle are populated before execute() is called

4. Tab-Completion

Tab-completion uses the same MuParser with a CompletionInfo collector. When parsing reaches a partial token and needs an argument:

  1. MuParser calls Argument.complete(completions, partial, flags)
  2. The Argument subtype provides domain-specific completions (e.g., FileArgument lists files in current directory)
  3. Completions are added to CompletionInfo and presented to the user

Argument Flags

Arguments use bitwise flags to constrain behavior:

Flag Meaning
MANDATORY Must be supplied; validation fails if missing
OPTIONAL May be omitted (default if neither set)
SINGLE At most one value (default if neither set)
MULTIPLE Zero or more values allowed
EXISTING Value must denote an existing entity (file, device, etc.)
NONEXISTENT Value must NOT exist (for create operations)

Flags can be combined: Argument.MANDATORY | Argument.MULTIPLE requires at least one value.

Built-in Argument Types

The syntax package provides many Argument subclasses:

  • Value types: StringArgument, IntegerArgument, LongArgument
  • Files: FileArgument (validates file existence, type)
  • Network: HostNameArgument, PortNumberArgument, URLArgument
  • System: ThreadNameArgument, PropertyNameArgument, PluginArgument
  • Special: FlagArgument (boolean presence), EnumArgument (enum values), MappedArgument

Registration via Plugin System

Syntaxes can be declared declaratively in plugin descriptors for non-native commands:

<extension point="org.jnode.shell.syntaxes">
  <argument-bundle alias="mycmd">
    <argument label="name" class="org.jnode.shell.syntax.StringArgument" flags="MANDATORY"/>
  </argument-bundle>
</extension>

Gotchas & Non-Obvious Behavior

  • Labels must be unique within an ArgumentBundle; the parser uses labels to match Syntax nodes to Argument instances
  • Backtracking modifies state — MuParser tracks which Arguments were modified and calls undoLastValue() when backtracking; do not rely on side effects during validation
  • Step limit — MuParser has a DEFAULT_STEP_LIMIT of 10000 to prevent pathological grammars from hanging; complex commands with many alternatives may need adjustment
  • EXISTING/NONEXISTENT are not logical negations — they check different properties; a file can be both non-existent (for creation) and non-accessible (for read)
  • Option syntax is just syntaxOptionSyntax compiles to a MuSequence containing MuSymbol and MuArgument; you can achieve the same with manual symbol/argument sequences
  • Completion is partial-parse — when completing, MuParser explores all alternatives that could match the partial token; arguments providing completions must avoid suggestions that would fail validation in doAccept()
  • Child syntaxes inherit parent labels — labels from child Syntax nodes must match Argument labels in the bundle, even across module boundaries

Related Pages

  • Shell-Syntax — Shell syntax parsing infrastructure, MuParser, SyntaxManager, and registration
  • Shell-Commands — Parent hub page for the command framework
  • Plugin-System — How commands and syntaxes are registered via extensions
  • Code-Conventions — Best practices for writing JNode commands

Clone this wiki locally