Linguistic Symbiosis

Symbiosis with Smalltalk

As introduced while discussing the syntax of SOUL, SOUL variables can be bound to Smalltalk values. Moreover, SOUL supports embedding Smalltalk expressions in its queries. Such Smalltalk terms are delimited by square brackets and can contain logic variables wherever Smalltalk variables are allowed. Examples include [3.4 asInteger], [Object] and [?x > ?y].

Smalltalk terms within a SOUL query are evaluated as standard Smalltalk expressions, after logic variables have been substituted by the values they are bound to. This happens when a Smalltalk term has to be unified with another term and when a Smalltalk term has to be resolved:

  • A Smalltalk term (e.g. the argument of a logic condition) unifies with another term (e.g. the parameter of the head of a logic rule) if its expression evaluates to a value that unifies with the term.
  • A Smalltalk term can also be used as a condition on its own, in which case its expression has to evaluate to the the boolean true in order for resolution to succeed. In practice, it is not necessary to know when the expression in a Smalltalk term is evaluated unless it has side-effects.

The hybrid language characteristic of SOUL influences the design of its logic libraries. Predicate contains:/2, for instance, is equivalent to the member/2 predicate of Prolog. In addition to the membership relation between logic lists and their elements, contains:/2 captures the membership relation between Smalltalk collections and their elements. Variable bindings ?x→'abba', ?x→3 therefore comprise the solutions to the following query:

if [Array with: 'abba' with: 3] contains: ?x

The LiCoR library of predicates for querying Smalltalk programs relies heavily on SOUL's symbiosis with Smalltalk. Predicate isClass/1, for instance, is implemented along the following lines:

?class isClass if
  [Smalltalk allClasses] contains: ?class

SOUL's symbiosis with Smalltalk enables predicate isClass/1 to bind its argument to an actual Smalltalk class rather than a logic fact that represents the class. As a result, solutions to SOUL queries can be perused by the Smalltalk IDE in a straightforward manner. Moreover, SOUL is immediately aware of any change to a program within the IDE.

Symbiosis with Java

The JavaConnect library enables Smalltalk applications to invoke methods on any Java object in a running JVM. We have combined the existing symbiosis between SOUL and Smalltalk with the symbiosis between Smalltalk and Java provided by JavaConnect. This transitively establishes a symbiosis between SOUL and Java. To illustrate the resulting symbiosis, consider the following additional clause for the contains:/2 predicate of the SOUL standard library. This clause quantifies over all elements in a Java collection. The pre-existing clauses already quantified transparently over logic lists and Smalltalk collections (see above).

?collection contains: ?element if
  ?iterator equals: [?collection iterator],
  [?iterator hasNext],
  ?iterator iteratorPointsTo: ?element

?iterator iteratorPointsTo: ?element if
  ?element equals: [?iterator next]
?iterator iteratorPointsTo: ?element if
  [?iterator hasNext],
  ?iterator iteratorPointsTo: ?element

On line 2, ?iterator is bound to a Smalltalk proxy that wraps a Java iterator and forwards messages to it. Examples of such messages include iterator and hasNext. Smalltalk proxies for Java objects are automatically created by JavaConnect.

The Cava library of predicates for querying Java programs relies heavily on SOUL's symbiosis with Java. Cava foregoes the prevalent transcription of AST nodes to compound terms (e.g., a database of logic facts). Instead, the predicates of the Cava library query the AST nodes of the program directly. This is illustrated by the following SOUL query which uses predicates isMethodDeclaration/1 and methodDeclarationHasModifiers/2 from the Cava library:

if ?m isMethodDeclaration,
   ?m methodDeclarationHasModifiers: ?mods,
   [?mods isEmpty]

Its solutions consist of methods that have been declared without modifiers. Upon backtracking over the first condition, ?m gets bound successively to each org.eclipse.jdt.core.dom.MethodDeclaration instance among the AST nodes of the program under investigation. SOUL's symbiosis with Java enables predicate isMethodDeclaration/1 to bind its argument to an actual Java object rather than a logic fact that represents this object. This is illustrated further by the other conditions. The second condition binds ?mods to the ASTNode$NodeList (an innerclass of org.eclipse.jdt.core.dom.ASTNode) that represents the modifiers ?m has been declared with. The third condition sends a message isEmpty to this list. It is answered with a Java boolean. Through JavaConnect's automatic conversion to the equivalent Smalltalk boolean, this answer determines whether the third condition succeeds.