Hovering is great for small constructs, but as they get larger and the corresponding opening brace is off the top of the screen, it becomes problematic. Some might argue that should code should be refactored and broken down into smaller pieces, however, that is not always the case. Consider the following implementation of a "factor" in an expression parser (using a pattern matching library I've developed):
//----------------------------------------------
//
// Expression-
//
// Parser for an arithmetic expression parser.
// Demonstrates how to extend the Pattern class.
//
// Grammar:
//
// Expr -> addOp
//
// addOp -> mulOp addop'
// addOp' -> + mulOp addOp'
// -> - mulOp addOp'
// -> [empty]
//
// mulOp -> factor mulOp'
// mulOp' -> * factor mulOp'
// -> / factor mulOp'
// -> [empty]
//
// factor -> value
// -> - factor
// -> + factor
// -> ( Expr )
//
// For more information on this
// grammar, see "Compilers: Principles,
// Techniques, and Tools" by Aho, Sethi,
// and Ullman (the "Dragon Book").
public class Expression: Pattern
{
// exprStk holds temporary values
// during expression parsing:
var exprStk = Stack<Double>()
// factor-
// Handles floating-point literal constants
// and parenthetical expressions.
//
// Note: the parenthetical expressions
// are the magic part of this example.
// This demonstrates recursion, meaning
// the pattern-matching code supports
// context-free languages, making this
// pattern matching library more powerful
// than the standard Swift regular expression
// library (RegexBuilder).
//
// Handles the productions:
//
// factor -> value
// -> - factor
// -> + factor
// -> ( Expr )
open func factor( _ tail: @escaping PatClosure = {true} )->Bool
{
let saveStart = startIndex
let saveEnd = endIndex
var result = false
let val = Ref(.dbl)
val.d = 0.0
assert(
startIndex >= matchStr.startIndex
&& startIndex <= matchStr.endIndex
&& endIndex >= matchStr.startIndex
&& endIndex <= matchStr.endIndex,
"factor: String index bounds error"
)
result =
zeroOrMore(.whitespace)
{
// This code demonstrates alternation using
// both Boolean expressions and the choiceOf
// method. Alternative 1 uses a nested
// alternative (via choiceOf).
// Alternative 1: a floating-point value:
floatingPoint( val )
{
exprStk.push( val.d )
return !failFence && tail() && !failFence
}
// Alternative 2: a parenthetical expression:
|| c("(")
{
let value=Ref(.dbl)
let result =
expr( value )
{
zeroOrMoreWS(){
c(")")
{
exprStk.push( value.d )
return tailGate( tail )
}
}
}
return result
}
// Alternative 3: negation operation:
|| c("-")
{
let value=Ref(.dbl)
let result =
zeroOrMoreWS()
{
factor()
{
value.d = self.exprStk.pop()!
self.exprStk.push( -value.d )
return self.tailGate( tail )
}
}
return result
}
// Alternative 3: '+' prefix operation:
|| c("+")
{
let result =
zeroOrMoreWS()
{
factor()
{
return self.tailGate( tail )
}
}
return result
}
}
return restoreIndexes(
result,
saveStart,
saveEnd
)
} // end faster
// Other non-terminals handled down here, snipped for conciseness.
In pattern matching code using this scheme, each pattern matching function (which has a trailing closure) introduces a new closing parethesis at the end. In some patterns I've created (granted, expr is not one of them), I've had a dozen or more trailing closing braces. Furthermore, unlike nested statements, indentation isn't always an appropriate style -- these are not nested statements (I typical add indentation only to make it easier to keep track of the closing braces, not to indicate any structure in the algorithm).
For example, here's a pattern that matches strings from the regular language ( ^a{4}[aA][aA]{3}$)
p = Pattern()
pResult :Bool =
p.match( "aaaaAAAA" ){
p.nChar( "a", n:4 ) {
p.nChari( "a", n:1 ) {
p.nChari( "a", n:3 ) {
p.eos() // Note: default trailing closure is {true} here
}}}
}
Dealing with the trailing braces becomes problematic; just because you have to count the braces at the end. It would be much nicer to write something like:
p = Pattern()
pResult :Bool =
p.match( "aaaaAAAA" ){*
p.nChar( "a", n:4 ) {
p.nChari( "a", n:1 ) {
p.nChari( "a", n:3 ) {
p.eos() // Note: default trailing closure is {true} here
*}
FWIW, each pattern matching function returns true if it, and every following function, succeeds. The reason for the trailing closures is to allow backtracking via recursive calls.