Options
All
  • Public
  • Public/Protected
  • All
Menu

Class Expression

The core type of my AST, this data-structure represents a single (sub-)command in an excmd script; and is the product of entry-points like [[Parser.expressionOfString]].

Construction

This JavaScript class, like most in my interface, cannot be constructed directly (i.e. new Expression() is going to throw an error); instead, you must invoke the parser via the functions in the exported Parser module.

Evaluation

The language accepted by this parser allows for "sub-expressions" — runs of code that may remain unevaluated, to be executed at a later point.

The most direct method of interacting with these, is manually testing whether a particular method has produced a [[Literal]] or a [[Sub]]-expression, and proceeding as appropriate:

const expr = Parser.expressionOfString('(echo test_cmd) arg'),
   cmd = expr.command

switch (cmd.type) {
   case 'literal': console.log(cmd.value) // ... use stringish val,
   case 'subexpression': handleSubexpr(cmd.expr) // or evaluate.
}

However, this can be tedious, if you only wish to evaluate simple expressions. For these situations, I provide a simplified "recursive evaluation" interface: by inverting control, you can allow the parser to become responsible for recursively evaluating [[Sub]] and [[Literal]] values into simple strings, as long as you can provide the parser with a simplified handler-callback that will attempt to reduce a single, given sub-expression into a string.

The entry-point to this auto-evaluation interface is either cloneWithEvaluator, or as a further convenience, the various eval* methods below: evalCommand, evalPositionals, and so on.

Each of these takes a function — matching the [["index".evaluator | evaluator]] signature — that takes an expression, and mechanistically reduces it into a simple string:

const expr = Parser.expressionOfString('(echo test_cmd) arg')

function handleSubexpr(expr) {
   if (expr.command === 'echo') {
      return expr.positionals.join(' ')
   }
}

console.log("Command:", expr.evalCommand(handleSubexpr))
//=> prints "Command: test_cmd"

(Note, additionally, that within the body of the evaluator above, I've only had to compare expr.command to a string, directly. As evaluators receive ExpressionEvals instead of plain expressions, their bodies need no further shenanigans; any requested expression-properties will recursively evaluate further subexpressions as-needed.)

Finally, if you want to access multiple elements of an expression thusly, it may be easier to pre-associate an evaluator with the expression using cloneWithEvaluator, producing an ExpressionEval directly:

function handleSubexpr(expr) {
   if (expr.command === 'echo') {
      return expr.positionals.join(' ')
   }
}

const expr = Parser.expressionOfString('(echo test_cmd) arg'),
   ee = expr.cloneWithEvaluator(handleSubexpr)

console.log(`Command: ${ee.command}, args: ${ee.positionals.join(' ')}`)
//=> prints "Command: test_cmd, args: arg"

Mutation

Importantly, these Expressions are mutable views onto the underlying AST. They're designed to ensure you don't access the data-structure in an unsafe way. In particular, a given word in the original input-string can only be either a flag-payload, or a positional argument — not both.

Consider the following:

const expr = Parser.expressionOfString('hello -f world')

Does that command have one flag, with the payload world? Or an empty (boolean) flag, and a single positional-argument, world? My answer, in this interface, is “whichever you observe first”. To be more specific, any method of either Expression or ExpressionEval that produces flag-payloads or positional arguments, will mutate the data-structure such that subsequent accesses don't see an inconsistent state.

Let's look at some examples:

const expr1 = Parser.expressionOfString('hello -f world')

expr1.getFlag('f').value //=> returs 'world';
expr1.getPositionals() //=> returns []

Here, getFlag mutated expr1; and so a subsequent getPositionals produced a consistent result: there were no remaining positional arguments.

const expr2 = Parser.expressionOfString('hello -f world')

expr2.getPositionals() //=> returns one arg, [{ value: 'world' }]
expr2.getFlag('f') //=> returs undefined; because `-f` has no payload

Here, getPositionals mutated expr2; and so a subsequent getFlag, again, produced a consistent result: there was no value available to become the payload of -f.

This interface is intended to be used in a form wherein the invocations of these methods acts as the specification of the parser's behaviour. For example, one command might expect a flag -d or --dest, and demand a payload for that flag; whereas another might use -d as a boolean, and expect positionals. This way, the behaviour of the parser is flexible enough to handle both of those situations.

Finally, note that at least for flags, there's also non-mutative accessors: you can always check whether a flag exists using hasFlag.

Hierarchy

  • ExpressionCommon
    • Expression

Index

Accessors

command

count

  • get count(): number

flags

  • get flags(): string[]

Methods

clone

cloneWithEvaluator

cloneWithEvaluatorSync

evalCommand

  • evalCommand(handleSubexpr: evaluator): Promise<string>
  • Parameters

    • handleSubexpr: evaluator

    Returns Promise<string>

evalCommandSync

  • evalCommandSync(handleSubexpr: evaluatorSync): string
  • Parameters

    • handleSubexpr: evaluatorSync

    Returns string

evalEachFlag

  • evalEachFlag(handleSubexpr: evaluatorSync, f: (name: string, payload: string, idx: number) => void): void
  • Parameters

    • handleSubexpr: evaluatorSync
    • f: (name: string, payload: string, idx: number) => void
        • (name: string, payload: string, idx: number): void
        • Parameters

          • name: string
          • payload: string
          • idx: number

          Returns void

    Returns void

evalFlag

  • evalFlag(handleSubexpr: evaluator, flag: string): Promise<string>
  • Parameters

    • handleSubexpr: evaluator
    • flag: string

    Returns Promise<string>

evalFlagSync

  • evalFlagSync(handleSubexpr: evaluatorSync, flag: string): string
  • Parameters

    • handleSubexpr: evaluatorSync
    • flag: string

    Returns string

evalPositionals

  • evalPositionals(handleSubexpr: evaluator): Promise<string[]>
  • Parameters

    • handleSubexpr: evaluator

    Returns Promise<string[]>

evalPositionalsSync

  • evalPositionalsSync(handleSubexpr: evaluatorSync): string[]
  • Parameters

    • handleSubexpr: evaluatorSync

    Returns string[]

forEachFlag

  • forEachFlag(f: (name: string, payload: word<Expression>, idx: number) => void): void
  • Parameters

    • f: (name: string, payload: word<Expression>, idx: number) => void
        • (name: string, payload: word<Expression>, idx: number): void
        • Parameters

          • name: string
          • payload: word<Expression>
          • idx: number

          Returns void

    Returns void

getFlag

getPositionals

hasFlag

  • hasFlag(flag: string): boolean
  • Parameters

    • flag: string

    Returns boolean

Generated using TypeDoc