Author image Jeff Goff (PAUSE Custodial Account)


Text::PromptBalanced - Aid in creating CLI prompts that keep track of balanced text


  use Text::PromptBalanced qw(balance_factory);
  ($state,$balance) = balance_factory(
    string => { type => 'toggle', open => '"' },
    paren => {
      type => 'balanced', open => '(', close => ')', ignore_in => 'string' },
    comment => { type =>' eol', open => ';', ignore_in => 'string' },
    escape => { type => 'escape', open => '\\' }
  while(<STDIN>) {
    my $cur_balance = $balance->($_);
    if($state->{string}==1) { print q["> ] }
    elsif($state->{paren} > 0) { print qq[($cur_balance> ] }
    elsif($cur_balance < 0)
      { warn "Unbalanced paren at character $cur_balance" }
    else { print q[0> ] }


This is intended to be an aide to help generate the prompt strings that are presented by applications like Postgres' psql tool and various Lisp compilers.

Specifically, these types of applications have prompts that tell the user how deeply their parenthesis count is nested, and/or if they have closed strings yet. The sole subroutine in this module creates and returns a function which is designed to be called on every line of text a user inputs, and keeps track of whether the parentheses, braces, strings, or what-have-you match as the user types the input in.

For example, a typical interaction with the application might appear as follows:

  0> (+ 3 4)
  0> (+ (* 2 -1)
  1> 2)
  0> (print "(Hi there
  "> world")

Note that the prompt changes from "0> " to "1> " when the user fails to close a parenthesis. Also, later on notice that it informs the user that the string has not been finished, but ignores the ( inside the string. This behavior naturally is configurable when the parser is created.

The parser currently understands five basic types of nested and non-nested constructs. Nested constructs include parentheses, braces and occasionally brackets. Non-nested constructs can be things like UNIX and C comments, and strings.

To make things even more flexible, constructs can be selectively ignored inside others. For instance, you can choose to ignore (as we did above) parentheses when inside a string, or ignoring strings inside of a C comment.

Constructs are specified to the parser roughly as follows (Taking our example from earlier):

  ($state,$b) = balance_factory(
    parentheses => { # Give the type a name
      type => 'balanced', # Count occurrences of this type in and out
      open => '(', close => ')',
      ignored_in => 'string' }, # Ignore these when inside a string
    string => {
      type => 'toggle', # Occurrences of this type flip a toggle on and off.
      open => q<"> }, # Toggle on q<">.

This tells the parser to keep track of ( and ), as long as they're outside (which is a synonym for 'ignored_in', incidentally) a string. The string definition merely says to toggle the 'string' flag on and off as " is encountered while scanning the string. A running balance is kept as more lines get fed to $b-($input)>, and the $state variable changes as each new character sequence is encountered.

The $state is a hashref, with (in this case) keys 'parentheses' and 'string'. You can inspect or change this state as you desire, and set your prompt accordingly. The return values are in the reverse order from what you'd expect so that the code fragment $b = balance_factory(); works with minimal fuss, yet you can still get access to the internal state.


Useful for parentheses, braces and brackets, this keeps a running count of the number of left and right parentheses, with ( incrementing and ) respectively decrementing the count. If the count should ever go negative, the balance function returns a negative number equal to the position of the character in the input string.

A sample configuration looks like:

  brace => { type => 'balanced', open => '{', close => '}' }

C comments fall into this category, and not much else. This type has both an 'open' and 'close' specifier like its cousin balanced, but the count will never go higher than one, so /* /* foo */ would report as balanced, even though the "second /*" never gets balanced out. Sample configuration is the same as the balanced type.


Designed for UNIX and C++-style comments, a to-eol comment starts when the appropriate character (say, '#') is encountered, and runs until the end of the line, when it is turned off. Thus, it will never be a factor in balancing, but it still is represented by a state, in case you want to play with this.

Since its 'close' character is the end of the line, it need not be specified. When configuring for a comment, just specify:

  'c++-style' => { type => 'to-eol', open => '//' }

Toggles start out off, and are simply switched each time their character is encountered while scanning. The most common uses for this will be strings, most likely. Perl strings are probably beyond the scope of this utility, although patches are welcome...

Configure a string like this:

  'string' => { type => 'toggle', open => q<"> }

Escape characters behave just like they do in any language. An escape character before any special character (or any other, for that matter) nullifies any meaning it might have had. The escape character is common enough that it's given its own shortcut, escape = 1>. If you feel the need to specify a character other than \\, it follows the same format as the toggle above:

  'escape' => { type => 'escape', open => q<\\> }

The names 'comment' and 'escape' are considered special. To wit, if a 'comment' type is declared, all other types are ignored until the comment is finished. If for whatever reason you want to override this behavior, simply name your type something other than 'comment'.

The 'escape' type just needs to be defined as 'escape => 1' to get the common definition of \\. Other strings can be specified as desired. Last but not least, types can be ignored within other types.

For instance, you probably don't want to count parentheses inside strings. To arrange for this behavior, add ignore_in = 'string'> to the list of keys in 'parentheses'. If you want a type to be ignored within any of a list of other types, pass a list of those types instead of 'string'.


By default, balance_factory


Term::Readline, perl


Jeffrey Goff, <>


Copyright 2004 by Jeffrey Goff

This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.