Skip to main content

SWELL - Internals

The code described below is obviously SWELL-specific, but the overall organization is a generic blueprint (see Figure-1 below) for any DSL interpreter that uses VisualLangLab.

Interpreter blueprint
Figure 1. Interpreter blueprint

Disclaimer: Figure-1 just describes the dependencies between the various components. It does not imply that SWELL embeds any of the other components in any way.

As Figure-1 shows, the internal structure is very simple, just bundle your own code together with and any required libraries and the VisualLangLab API. In the case of SWELL, it depends on the ABBOT (http://abbot.sourceforge.net/) framework for Swing GUI testing, so those jars are required. All interpreters using the VisualLangLab API must perform the following steps:

  • Locate the grammar (XML) file
  • Regenerate the parser
  • Parse the given input, and obtain AST
  • Interpret the AST

The following sections show and explain the essential code that handles these functions. The first three steps (up to obtaining the AST) are handled in one block of code, and are explained under Locate Grammar ... Obtain the AST below. Interpretation of the AST is explained under Application-Code AST Interaction.

Locate Grammar ... Obtain AST

A parser developed with VisualLangLab is saved as XML in a grammar-file with a .vll type. A program that uses such a parser (like the SWELL interpreter) uses the VisualLangLab API to turn this XML into an instance of the Scala Parsers type. But it must first obtain the contents of the grammar file. In the case of the SWELL interpreter, the grammar file is included as a resource in the jar file, and is retrieved using the system classloader's getResourceAsStream(String) method. This approach is convenient for programs that use large grammar-files. Smaller grammar-files can be embedded as a string within the interpreter program itself. To examine the code that performs these functions, look around the def main(...) {...} function.

Application-Code AST Interaction

The SWELL interpreter is written in Scala, and exploits Scala's pattern matching capability to analyze and unravel the AST. The description below uses snippets of code from SWELL that show how the AST is unraveled in steps strongly reminiscent of the oft-quoted onion skin metaphor. The grammar-trees (and ASTs) involved are those depicted in Navigating grammar-tree references. The following function marks the beginning of the onion's odyssey. The expression res.get returns the AST (the entire onion), which is passed to the Suite_handler().

Those interested in reviewing the full code should visit the src directory of the SWELL.zip file.

  def main(args: Array[String]) {
      ... lines removed for clarity ...
    val res = parser(input)
    if (res.successful) {
      ... lines removed for clarity ...
      Suite_handler(res.get)
      ... lines removed for clarity ...
  }

The function Suite_handler() shown below processes the AST from the top-level parser-rule Suite, (the entire onion). The structure of Suite's AST is shown on the left below, and notice how the code uses pattern matching to separate the onion's layers. The part of the onion containing the tests is passed to the function handleTests().

ASTHandler Code
Array(
|  @stmtSuitBegins,
|  List(@groupDefinition),
|  Option(@stmtBeforeEachTest),
|  Option(@stmtAfterEachTest),
|  List(
|  |  Choice(
|  |  |  Pair(0, @singleTest),
|  |  |  Pair(1, @runCommand)
|  |  )
|  ),
|  @stmtSuitEnds
)
  private def Suite_handler(pTree: Any) {
    pTree match {
      case Array(suiteBegins, groups, beforeEachTest, 
          afterEachTest, tests, suiteEnds) =>
            ... lines removed for clarity ...
          handleTests(tests)
            ... lines removed for clarity ...
  }

The structure of the (part of the) AST passed to handleTests() is shown below left, and a part of handleTests()'s code is shown on the right. Notice the use of a cast and pattern-matching to locate the list of statements in each test. Each statement is passed off (one at a time) to handleRunStatements().

ASTHandler Code
Array(
|  @testHeader,
|  List(@runStatements)
)
  private def handleTests(tests: Any) {
    tests.asInstanceOf[List[_]].foreach(_ match {
        case Pair(0, Array(Array(testHeader: String, descrExprRes), 
            statements: List[_])) =>
                  ... lines removed for clarity ...
                handleRunStatements(statements)
  }

An abbreviated version of handleRunStatements()'s AST and code are shown below. Notice how pattern-matching is used to recognize each statement, and then a part of the sub-onion received is passed off to the appropriate handler function.

ASTHandler Code
Choice(
|  Pair(0, @stmtClearText),
    ... lines removed ...
|  Pair(6, @stmtEnterText),
    ... lines removed ...
|  Pair(18, @stmtMoveMouse),
    ... lines removed ...
)
  private def handleRunStatements(stmts: List[_]) {
          ... lines removed for clarity ...
        sz._1 match {
          case Pair(0, c2) => currentStatementName = "Clear Text"; 
              gd(); as(traceAll); stmtClearText(c2)
                ... lines removed for clarity ...
          case Pair(6, txt) => currentStatementName = "Enter Text"; 
              gd(); as(traceAll); stmtEnterText(txt)
                ... lines removed for clarity ...
          case Pair(18, pos) => currentStatementName = "Move Mouse"; 
              gd(); as(traceAll); stmtMoveMouse(pos)
        }
          ... lines removed for clarity ...
  }

The code below illustrates how the enter text statement is executed. The code seems to rely on java.awt.Components having an actionEnterText(String) method! To understand this, check out SWELL Document Object Model below.

An important part of the SWELL grammar is the SwingQuery, a construct that enables any GUI component to be referred using natural-English phrases.

ASTHandler Code
Array(
|  Choice(
|  |  Pair(0, [STRING]),
|  |  Pair(1, [VAR])
|  ),
|  @swingQuery
)
  private def stmtEnterText(txt: Any) = {
    txt match {
      case Array(value: Pair[Int, _], compChoice) =>
        Utils.swingQuery(compChoice) match {
          case c: Component => value match {
            case Pair(0, strStr: String) => 
              c.actionEnterText(Utils.unEscape(Utils.unQuote(strStr)))
            ... lines removed for clarity ...
            }
          case x => throw new NotAwtComponent(Utils.scalaTypeOf(x))
        }
    }
  }

Because Swing components are central to the purpose of SWELL, its parser-rules are peppered with references to swingQuery (see Swing-Query Syntax below). The article will not go into the details of the SwingQuery grammar, but readers are encouraged to analyze and test the grammar within the VisualLangLab IDE.

ASTHandler Code
List(
|  Choice(
|  |  Pair(0, @treePath),
|  |  Pair(1,
|  |  |  Array(
|  |  |  |  [INT],
|  |  |  |  [INT]
|  |  |  )
|  |  ),
|  |  Pair(2, @uniqueComponentSelector),
|  |  Pair(3, @methodCall),
|  |  Pair(4, [INLINE_CODE]),
|  |  Pair(5, @componentSelector),
|  |  Pair(6, [VAR])
|  )
)
  def swingQuery(mc: Any) = mc match {
    case tPath: List[Pair[Int, _]] =>
      var expr: Any = null
      tPath.reverse.foreach(x => x match {
          case Pair(0, tp: List[_]) => expr match {
            case jt: JTree => expr = treePath(jt, tp).last
            ... lines removed for clarity ...
          }
            ... lines removed for clarity ...
          case Pair(1, Array(r, c)) => 
            ... lines removed for clarity ...
          case Pair(2, ucs) => 
            ... lines removed for clarity ...
        })
      expr
  }

The AST and code below illustrate the last step in the rule chain.

ASTHandler Code
List(
|  Choice(
|  |  Pair(0, [STRING]),
|  |  Pair(1, [INT]),
|  |  Pair(2, [INLINE_CODE])
|  )
)
  def treePath(jt: JTree, path: List[_]) = {
    path.foreach(p => p match {
        case Pair(0, strStr: String) =>
          ... lines removed for clarity ...
        case Pair(1, strInt: String) => 
          ... lines removed for clarity ...
        case Pair(2, code: String) =>
          ... lines removed for clarity ...
      })
         ... lines removed for clarity ...
  }

SWELL has DOM and SQL (Swing Query Language) Too!

The term DOM is used here in a sense similar to the web-browser usage. The SWELL DOM is a conceptual tree that contains all of the GUI's visual java.awt.Components. The DOM is implemented by the RichComponent class in the SWELL sources (see the src directory inside SWELL.zip). RichComponent and its companion object leverage pimp my library (also see this paper from SigBovik 2010) to magically endow the lowly java.awt.Component with interesting methods and properties.

As explained above, a SwingQuery is a parser-rule (see Figure-2 below) that enables SWELL scripts to refer any GUI component using natural-English (Swing Query Language or SQL) phrases. SWELL's SQL is, in every sense, the heart of SWELL. The English-like character of SWELL scripts comes entirely from SQL. The power of the DOM would be completely wasted if the SWELL user wasn't able to identify a GUI element using an English-like phrase.

SwingQuery syntax
Figure 2. The Swing-Query syntax

The SQL grammar is based on the premise that the DOM is rooted at the top-level window. Any GUI Component can be uniquely identified by any one of the following:

  • a set of attribute values that uniquely identify the component in the local context
  • a path from the root to the Component of interest, where each intermediate step is a uniquely identified component

We give a few real examples here to clarify the concepts described above. The graphics in these examples are slightly modified versions of screens captured during tests on the CIIMS messaging-framework for airports from Unisys.

The following SWELL statements illustrate the use of SQL to work with the login dialog panel shown in Figure-3 below. In each SWELL statement, the underlined part is the SQL.

 - key "USER01" into the first textfield of the "Login" dialog
 - key "pass123" into the passwordfield of the "Login" dialog
 - key "FATS01" into the 2nd textfield of the "Login" dialog
 # the following 3 lines are equivalents, and any one may be used
 - click the 1st button of the "Login" dialog
 - click the first button of the "Login" dialog
 - click the "Login" button of the "Login" dialog
 - click the button with text = "Login" of the "Login" dialog

In all of the above SWELL statements, [... of the "Login" dialog] would have worked as well since there is only one visible dialog, and the type (dialog) itself identifies the component uniquely.

Locator syntax for GUI Components
Figure 3. Locator syntax for GUI Components

SQL can also be used with more complex GUI structures. The following two examples show how the syntax is used to access nodes of a JTree and cells of a JTable respectively.

Locator syntax for JTree elements
Figure 4. Locator syntax for JTree elements

Figure-4 above shows a JFrame main window containing a JInternalFrame that in turn contains a JTree. Parts of the figure are annotated with alphabetic markers to facilitate referencing from the following explanation.

  1. the JInternalFrame itself (A) is located with:
    the "Flow" internalframe
  2. the JTree (B) is located with:
    the tree of the "Flow" internalframe
  3. the "Events" node (C) of the JTree is located with:
    node / "Events" of the tree of the "Flow" internalframe
  4. the "TESTEVENT" node (D) of the JTree is located with:
    node / "Events" / "TESTEVENT" of the tree of the "Flow" internalframe
  5. the "TESTEVENT" node may also be located with:
    node / 2 / "TESTEVENT" of the tree of the "Flow" internalframe, or
    node / "Events" / 0 of the tree of the "Flow" internalframe, or
    node / 2 / 0 of the tree of the "Flow" internalframe

The JFrame main window in Figure-5 below contains a JInternalFrame that in turn contains a JTable. The alphabetic markers facilitate referencing from the following explanation.

Locator syntax for JTable elements
Figure 5. Locator syntax for JTable elements

  1. the JInternalFrame itself (A) is located with:
    the "Statistics" internalframe
  2. the Start button (B) is located with:
    the "Start" button of the "Statistics" internalframe
  3. the JTable (C) is located with:
    the table of the "Statistics" internalframe
  4. the outlined cell (D) of the JTable is located with:
    cell 17 1 of the table of the "Statistics" internalframe
 
 
Close
loading
Please Confirm
Close